目录
ResourceServerConfigurer
ResourceServerConfigurerAdapter
插曲:WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter对比
OAuth2AuthenticationProcessingFilter
实现ResourceServerConfigurerAdapter类重写方法自定义资源配置
上一篇写了授权服务器的配置,在Spring Security OAuth2.0可以把授权服务器(AuthorizationServer)和资源服务器(ResourceServer)配置在一个项目中,那这个服务即作为授权服务使用(提供登录、授权、token验证等),也可以使用Security 管理这个服务中的资源(指定受保护资源,白名单等);当然,这两个也可以单独配置在不同的项目中。
ResourceServerConfigurer
ResourceServerConfigurer接口是Spring Security OAuth2.0中的资源服务配置相关的核心配置接口。与AuthorizationServer类似,要配置ResourceServer的话需要在项目中添加@EnableResourceServer注解,并继承ResourceServerConfigurer接口的实现类ResourceServerConfigurerAdapter这个类重写适配器类的方法。
ResourceServerConfigurerAdapter
ResourceServerConfigurer接口的实现类,看一下ResourceServerConfigurerAdapter这个类结构。
public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
}
@Override
public void configure(HttpSecurity http) throws Exception {
//用于配置spring security oauth2中的接口资源访问权限
http.authorizeRequests().anyRequest().authenticated();
}
}
看到第二个configure方法觉得眼熟,在这里就涉及到Spring Security的资源配置类WebSecurityConfigurerAdapter,下面对比下这两个配置类。
插曲:WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter对比
首先,他们所属的功能模块不同,前者spring security的,后者是spring security oauth2里的。他们都是Adapter,对应都会产生一个filter Chain,他们两者可以相互配合来对不同的Url进行权限控制。需要注意的是如果项目中同时配置了这两个配置,由于优先级的原因,ResourceServerConfigurerAdapter中的configure(HttpSecurity http)方法中url配置会覆盖WebSecurityConfigurerAdapter中configure(HttpSecurity http)中的url。
关于WebSecurityConfigurerAdapter详细的配置在Spring Security自定义配置文章中。
OAuth2AuthenticationProcessingFilter
对于我们配置好的一些限定的受保护的资源,必须要得到认证后才能访问。那么OAuth2AuthenticationProcessingFilter这个过滤器就是认证Filter chain中重要的一个过滤器。简述一下它的主要作用:
对请求进行认证校验
从请求头中获取Authorization对应的token值并封装成Authentication,然后通过AuthenticationManager接口实例(OAuth2AuthenticationManager)的authenticate方法进行认证校验。逻辑其实就是到TokenStore中拿到这个token对应的认证信息,如果没有拿到就是认证失败,抛对应的异常直接返回,例如:Invalid access token: 6d77b1e8-6af6-4ac3-85c8-24ca93b0f036。
维护SecurityContext中的Authentication
在Security Oauth2权限体系的应用中,我们获取登录用户的认证信息有两种方式:
1.使用SecurityContextHolder.getContext().getAuthentication()来获取。
2.在 Controller 的方法中,加入 Authentication 参数
这里要说的就是 我们之所以能通过第一种方式拿到信息,是因为OAuth2AuthenticationProcessingFilter这个过滤器会对每一个请求都会维护SecurityContext这个对象,详细说就是请求的token认证通过获得认证信息后,就会把认证信息Authentication放入SecurityContext中。所以能在应用中获取当前登录用户。SecurityContext应该是存在于ThreadLocal
中的,这样能保证不同用户拿到的都是自己的认证信息,同时也要注意获取登录信息时只能在当前请求线程中才拿得到。
实现ResourceServerConfigurerAdapter类重写方法自定义资源配置
@Configuration
@EnableResourceServer
@EnableConfigurationProperties(PermitUrlProperties.class)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private PermitUrlProperties permitUrlProperties;
@Autowired(required = false)
private TokenStore tokenStore;
@Autowired
private ObjectMapper objectMapper; //springmvc启动时自动装配json处理类
@Autowired
private OAuth2WebSecurityExpressionHandler expressionHandler;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
if (tokenStore != null) {
resources.tokenStore(tokenStore);
}
resources.stateless(true);
resources.expressionHandler(expressionHandler);
// 自定义异常处理端口 AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
resources.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
Map<String, String> rsp = new HashMap<>();
response.setStatus(HttpStatus.UNAUTHORIZED.value());
rsp.put("code", HttpStatus.UNAUTHORIZED.value() + "");
rsp.put("msg", authException.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(rsp));
response.getWriter().flush();
response.getWriter().close();
}
});
//AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
resources.accessDeniedHandler(new OAuth2AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException) throws IOException, ServletException {
Map<String, String> rsp = new HashMap<>();
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
rsp.put("code", HttpStatus.UNAUTHORIZED.value() + "");
rsp.put("msg", authException.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(rsp));
response.getWriter().flush();
response.getWriter().close();
}
});
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(
new RequestMatcher() {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public boolean matches(HttpServletRequest request) {
// 请求参数中包含access_token参数
if (request.getParameter(OAuth2AccessToken.ACCESS_TOKEN) != null) {
return true;
}
// 返回true的,不会跳转login.html页面
if (antPathMatcher.match(request.getRequestURI(), "/auth-server/oauth/userinfo")) {
return true;
}
return false;
}
}
).authorizeRequests().antMatchers(permitUrlProperties.getIgnored()).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.anyRequest()
.authenticated();
}
}