I_d*_*now 1 java authentication spring spring-security
我从各种资源中了解了Spring Security,我知道过滤器和身份验证管理器如何分别工作,但是我不确定请求与它们一起工作的确切顺序。如果我没看错,总之,请求首先通过过滤器,然后过滤器调用它们各自的身份验证管理器。
我想允许两种身份验证-一种使用JWT令牌,另一种使用用户名和密码。以下是来自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" />
Run Code Online (Sandbox Code Playgroud)
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));
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
通过myAuthenticationProvider我想要基于用户名密码的身份验证并通过自定义过滤器,我想检查JWT令牌。有人可以让我知道我是否朝正确的方向前进吗?
广义上讲,具有多个AuthenticationProvider的要求分为两类:
/web/**使用基于表单的用户名密码验证的请求;/api/**使用基于令牌的身份验证对所有请求进行身份验证。每个解决方案都略有不同,但是它们基于共同的基础。
Spring Security对基于表单的用户名-密码身份验证提供了开箱即用的支持,因此,不管以上两个类别如何,这都可以轻松实现。
但是,不支持立即使用基于令牌的身份验证,因此需要自定义代码才能添加必要的支持。添加此支持需要以下组件:
AbstractAuthenticationToken,它将保存令牌以用于认证。AbstractAuthenticationProcessingFilter该过滤器将从请求中提取令牌值并填充在上面的步骤1中创建的POJO。AuthenticationProvider将使用令牌对请求进行身份验证的实现。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; }
}
Run Code Online (Sandbox Code Playgroud)
AbstractAuthenticationProcessingFilter需要过滤器才能从请求中提取令牌。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,过滤器不会尝试执行身份验证;而是将实际身份验证委派给AuthenticationManager,以确保也可以正确执行身份验证之前和之后的所有步骤。
AuthenticationProvider该AuthenticationProvider负责执行身份验证的实际组成部分。AuthenticationManager如果配置正确,它将由自动调用。一个简单的实现如下所示:
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;
...
}
}
Run Code Online (Sandbox Code Playgroud)
http为每个URL系列使用不同的元素,例如:
<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>
Run Code Online (Sandbox Code Playgroud)
由于不同的URL家族要求使用不同的身份验证模式,因此我们需要两个不同的AuthenticationManagers和两个不同的http配置,每个URL家族一个。对于每一种,我们选择支持哪种身份验证模式。
使用单个http元素,如下所示:
<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>
Run Code Online (Sandbox Code Playgroud)
请注意以下几点:
AuthenticationManager不需要为http元素明确指定,因为配置中只有一个,并且其标识符为authenticationManager,这是默认值。AuthenticationManager被配置为使用多个AuthenticationProvider秒。这样可以确保尝试多种身份验证机制,直到找到请求支持的机制为止。我的方法是使用 2 个安全配置器。我有一个 Java 配置示例,但如果您理解它,您可以将其移植到 xml。请注意,这只是其中一种方法,而不是唯一方法。
@Configuration
@Order(1)
public static class LoginSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/login/**")
.authorizeRequests()
.antMatchers("/api/login/**").authenticated()
.and()
.httpBasic();
}
}
@Configuration
@Order(2)
public static class JWTSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and()
.addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Run Code Online (Sandbox Code Playgroud)
解释:
在 中LoginSecurityConfigurerAdapter,我只拦截api/loginurl。因此,第一次,登录请求将在此处捕获,身份验证成功后,您可以发出 JWT。现在JWTSecurityConfigurerAdapter,我正在捕获所有其他请求。使用 tokenauthenticationfilter 它将验证 JWT,并且只有在 JWT 有效的情况下,它才会允许访问 API。
| 归档时间: |
|
| 查看次数: |
2550 次 |
| 最近记录: |