如何使用 Spring Security 提供多种身份验证方式 [英] How to provide multiple ways of authentication with Spring Security
问题描述
我从各种资源中了解 Spring Security,我知道过滤器和身份验证管理器是如何分开工作的,但我不确定请求与它们一起工作的确切顺序.如果我没记错的话,简而言之,请求首先通过过滤器,过滤器调用它们各自的身份验证管理器.
I have about Spring Security from various resources and I know how filters and authentication managers work separately but I am not sure of the exact sequence in which a request works with them. If I am not wrong, in short the request first goes through the filters and the filters call their respective authentication managers.
我想允许两种身份验证 - 一种使用 JWT 令牌,另一种使用用户名和密码.以下是摘自 security.xml
I want to allow two kinds of authentication - one with JWT tokens and another with username and password. Below is the extract from security.xml
Security.xml
<http pattern="/api/**" create-session="stateless" realm="protected-apis" authentication-manager-ref="myAuthenticationManager" >
<csrf disabled="true"/>
<http-basic entry-point-ref="apiEntryPoint" />
<intercept-url pattern="/api/my_api/**" requires-channel="any" access="isAuthenticated()" /> <!-- make https only. -->
<custom-filter ref="authenticationTokenProcessingFilter" position = "FORM_LOGIN_FILTER"/>
</http>
<beans:bean id="authenticationTokenProcessingFilter"
class="security.authentication.TokenAuthenticationFilter">
<beans:constructor-arg value="/api/my_api/**" type="java.lang.String"/>
</beans:bean>
<authentication-manager id="myAuthenticationManager">
<authentication-provider ref="myAuthenticationProvider" />
</authentication-manager>
<beans:bean id="myAuthenticationProvider"
class="security.authentication.myAuthenticationProvider" />
MyAuthenticationProvider.java
public class MyAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Code
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter{
protected TokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl); //defaultFilterProcessesUrl - specified in applicationContext.xml.
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); //Authentication will only be initiated for the request url matching this pattern
setAuthenticationManager(new NoOpAuthenticationManager());
setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
}
/**
* Attempt to authenticate request
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException,
IOException,
ServletException {
String tid = request.getHeader("authorization");
logger.info("token found:"+tid);
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(tid,request);
if(userAuthenticationToken == null) throw new AuthenticationServiceException("Invalid Token");
return userAuthenticationToken;
}
/**
* authenticate the user based on token
* @return
*/
private AbstractAuthenticationToken authUserByToken(String token,HttpServletRequest request) throws
JsonProcessingException {
if(token==null) return null;
AbstractAuthenticationToken authToken =null;
boolean isValidToken = validate(token);
if(isValidToken){
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
authToken = new UsernamePasswordAuthenticationToken("", token, authorities);
}
else{
BaseError error = new BaseError(401, "UNAUNTHORIZED");
throw new AuthenticationServiceException(error.getStatusMessage());
}
return authToken;
}
private boolean validate(String token) {
if(token.startsWith("TOKEN ")) return true;
return false;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
通过 myAuthenticationProvider
我想要基于用户名密码的身份验证 &通过我想检查 JWT 令牌的自定义过滤器.有人可以让我知道我是否朝着正确的方向前进?
Through myAuthenticationProvider
I want username-password based authentication & through the custom filter I want to check for JWT tokens. Can someone let me know if I am going in the right direction?
推荐答案
解决方案概览
<小时>从广义上讲,需要有多个 AuthenticationProvider
分为两类:
- 使用不同的认证方式对不同类型的URL的请求进行认证,例如:
- Authenticate requests for different types of URLs with different authentication modes, for example:
- 使用基于表单的用户名-密码身份验证对
/web/**
的所有请求进行身份验证; - 使用基于令牌的身份验证对
/api/**
的所有请求进行身份验证.
- Authenticate all requests for
/web/**
using form-based username-password authentication; - Authenticate all requests for
/api/**
using token-based authentication.
每个人的解决方案略有不同,但都基于共同的基础.
The solutions are slightly different for each, but they are based on a common foundation.
Spring Security 对基于表单的用户名-密码身份验证提供开箱即用的支持,因此无论上述两种类别如何,都可以轻松实现.
Spring Security has out-of-the-box support for form-based username-password authentication, so regardless of the two categories above, this can be implemented quite easily.
然而,不支持开箱即用的基于令牌的身份验证,因此需要自定义代码来添加必要的支持.添加此支持需要以下组件:
Token-based authentication however, is not supported out-of-the-box, so custom code is required to add the necessary support. The following components are required to add this support:
- 扩展
AbstractAuthenticationToken
的 POJO,它将持有用于身份验证的令牌. - 扩展
AbstractAuthenticationProcessingFilter
的过滤器,它将从请求中提取令牌值并填充在上述第 1 步中创建的 POJO. - 一个
AuthenticationProvider
实现,将使用令牌对请求进行身份验证. - 上述选项 1 或 2 的 Spring Security 配置,具体取决于要求.
- A POJO extending
AbstractAuthenticationToken
that will hold the token to use for authentication. - A filter extending
AbstractAuthenticationProcessingFilter
that will extract the token value from the request and populate the POJO created on step 1 above. - An
AuthenticationProvider
implementation that will authenticate requests using the token. - Spring Security configuration for option 1 or 2 above, depending on the requirement.
AbstractAuthenticationToken
<小时>POJO 需要持有用于验证请求的 JWT 令牌,因此,最简单的 AbstractAuthenticationToken
实现可能如下所示:
public JWTAuthenticationToken extends AbstractAuthenticationToken {
private final String token;
JWTAuthenticationToken(final String token, final Object details) {
super(new ArrayList<>());
this.token = token;
setAuthenticated(false);
setDetails(details);
}
@Override
public Object getCredentials() { return null; }
@Override
public String getPrincipal() { return token; }
}
AbstractAuthenticationProcessingFilter
<小时>需要过滤器才能从请求中提取令牌.
AbstractAuthenticationProcessingFilter
A filter is required to extract the token from the request.
public class JWTTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public JWTTokenAuthenticationFilter (String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
@Override
public Authentication attemptAuthentication(final HttpServletRequest request
, final HttpServletResponse response)
throws AuthenticationException {
final JWTAuthenticationToken token = new JWTAuthenticationToken(/* Get token from request */
, authenticationDetailsSource.buildDetails(request));
return getAuthenticationManager().authenticate(token);
}
}
请注意,过滤器不会尝试执行身份验证;相反,它将实际的身份验证委托给 AuthenticationManager
,以确保任何前置和后置身份验证步骤也能正确执行.
Notice that the filter does not attempt to perform the authentication; rather, it delegates the actual authentication to an AuthenticationManager
, which ensures that any pre and post authentication steps are also performed correctly.
AuthenticationProvider
是负责执行身份验证的实际组件.如果配置正确,它会被 AuthenticationManager
自动调用.一个简单的实现如下所示:
The AuthenticationProvider
is the actual component responsible for performing the authentication. It is called by the AuthenticationManager
automatically, if configured properly. A simple implementation would look like:
public class JWTAuthenticationProvider implements AuthenticationProvider {
@Override
public boolean supports(final Class<?> authentication) {
return (JWTAuthenticationToken.class.isAssignableFrom(authentication));
}
@Override
public Authentication authenticate(final Authentication authentication)
throws AuthenticationException {
final JWTAuthenticationToken token = (JWTAuthenticationToken) authentication;
...
}
}
Spring Security 配置针对不同 URL 的不同认证方式
<小时>为每个 URL 系列使用不同的 http
元素,例如:
<bean class="com.domain.path.to.provider.FormAuthenticationProvider" "formAuthenticationProvider" />
<bean class="com.domain.path.to.provider.JWTAuthenticationProvider" "jwtAuthenticationProvider" />
<authentication-manager id="apiAuthenticationManager">
<authentication-provider ref="jwtAuthenticationProvider" />
</authentication-manager>
<authentication-manager id="formAuthenticationManager">
<authentication-provider ref="formAuthenticationProvider" />
</authentication-manager>
<bean class="com.domain.path.to.filter.JWTAuthenticationFilter" id="jwtAuthenticationFilter">
<property name="authenticationManager" ref="apiAuthenticationManager" />
</bean>
<http pattern="/api/**" authentication-manager-red="apiAuthenticationManager">
<security:custom-filter position="FORM_LOGIN_FILTER" ref="jwtAuthenticationFilter"/>
...
</http>
<http pattern="/web/**" authentication-manager-red="formAuthenticationManager">
...
</http>
由于不同的 URL 族需要不同的认证模式,我们需要两个不同的 AuthenticationManager
和两个不同的 http
配置,每个 URL 族一个.对于每个,我们选择支持哪种身份验证模式.
Since different authentication modes are required for different URL families, we need two different AuthenticationManager
s and two different http
configurations, one for each of the URL families. For each, we choose which mode of authentication is supported.
使用单个 http
元素,如下所示:
Use a single http
element, as follows:
<bean class="com.domain.path.to.provider.FormAuthenticationProvider" "formAuthenticationProvider" />
<bean class="com.domain.path.to.provider.JWTAuthenticationProvider" "jwtAuthenticationProvider" />
<authentication-manager id="authenticationManager">
<authentication-provider ref="formAuthenticationProvider" />
<authentication-provider ref="jwtAuthenticationProvider" />
</authentication-manager>
<bean class="com.domain.path.to.filter.JWTAuthenticationFilter" id="jwtAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<http pattern="/**">
<security:custom-filter after="FORM_LOGIN_FILTER" ref="jwtAuthenticationFilter"/>
...
</http>
注意以下几点:
AuthenticationManager
不需要为http
元素明确指定,因为配置中只有一个,它的标识符是authenticationManager
,它是默认值.- 令牌过滤器插入表单登录过滤器之后,而不是替换它.这可确保表单登录和令牌登录都有效.
AuthenticationManager
被配置为使用多个AuthenticationProvider
.这可确保尝试多种身份验证机制,直到找到一个请求支持的机制.
AuthenticationManager
need not be explicitly specified for thehttp
element as there is only one in the configuration and its identifier isauthenticationManager
, which is the default value.- The token filter is inserted after the form login filter, instead of replacing it. This ensures that both form login and token login will work.
- The
AuthenticationManager
is configured to use multipleAuthenticationProvider
s. This ensures that multiple authentication mechanisms are tried, until one is found that is supported for a request.
这篇关于如何使用 Spring Security 提供多种身份验证方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!