目录

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这个类重写适配器类的方法。

ResourceManager端口设置 resource server_Server

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();
	}

}