oauth过滤login_Spring Security Oauth2 之 核心架构配置

1 ResourceServerConfigurerAdapter (资源服务器配置)

内部关联了ResourceServerSecurityConfigurer和HttpSecurity。前者与资源安全配置相关,后者与http安全配置相关@Override

public void configure(ResourceServerSecurityConfigurer resources) {

//resourceId 用于分配给可授予的clientId

//stateless  标记以指示在这些资源上仅允许基于令牌的身份验证

//tokenStore token的存储方式(上一章节提到)

resources.resourceId(RESOURCE_ID).stateless(true).tokenStore(tokenStore)

//authenticationEntryPoint  认证异常流程处理返回

//tokenExtractor            token获取方式,默认BearerTokenExtractor

//                         从header获取token为空则从request.getParameter("access_token")

.authenticationEntryPoint(authenticationEntryPoint).tokenExtractor(unicomTokenExtractor);

}

其他属性:

accessDeniedHandler          权失败且主叫方已要求特定的内容类型响应

resourceTokenServices        加载 OAuth2Authentication 和 OAuth2AccessToken 的接口

eventPublisher                     事件发布-订阅  根据异常的clazz触发不同event@Configuration

@EnableResourceServer

protected class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

@Override

public void configure(HttpSecurity http) throws Exception {

AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);

//OAuth2核心过滤器

resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();

resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);

//OAuth2AuthenticationManager,只有被OAuth2AuthenticationProcessingFilter拦截到的oauth2相关请求才被特殊的身份认证器处理。

resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);

if (eventPublisher != null) {

//同上

resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);

}

if (tokenExtractor != null) {

//同上

resourcesServerFilter.setTokenExtractor(tokenExtractor);

}

resourcesServerFilter = postProcess(resourcesServerFilter);

resourcesServerFilter.setStateless(stateless);

if (!Boolean.TRUE.toString().equals(apolloCouponConfig.getOauthEnable())) {

// 不需要令牌,直接访问资源

http.authorizeRequests().anyRequest().permitAll();

} else {

http

//.anonymous().disable()  //匿名访问

.antMatcher("/**")        //匹配需要资源认证路径

.authorizeRequests()

.antMatchers("/swagger-ui.html", "/swagger-resources/**",

"/v2/api-docs/**", "/validatorUrl","/valid"

).permitAll()            //匹配不需要资源认证路径

.anyRequest().authenticated()

.and()

.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)

.exceptionHandling() //添加filter

.exceptionHandling().accessDeniedHandler(accessDeniedHandler)  //异常处理

.authenticationEntryPoint(authenticationEntryPoint);   //认证异常流程

}

}

}

accessDeniedHandler  异常 :  令牌不能访问该资源 (403)异常等

authenticationEntryPoint  异常 : 不传令牌,令牌错误(失效)等

2.AuthorizationServerConfig  认证服务器配置@Configuration

@EnableAuthorizationServer

public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

private static String REALM = "OAUTH_REALM";

/**

* 认证管理器,上一篇有涉及到,下面有具体描述

*/

@Autowired

@Qualifier("authenticationManagerBean")

private AuthenticationManager authenticationManager;

/**

* 获取用户信息

*/

@Autowired

private UserDetailsService userDetailsService;

/**

* 加密方式

*/

@Autowired

private PasswordEncoder passwordEncoder;

/**

* 数据源

*/

@Autowired

private DataSource dataSource;

/**

* 声明 ClientDetails实现 Load a client by the client id. This method must not return null.

* @return clientDetails

*/

@Bean

public ClientDetailsService clientDetails() {

return new JdbcClientDetailsService(dataSource);

}

/**

* 声明TokenStore实现

*

* @return TokenStore

*/

@Bean

public TokenStore tokenStore() {

return new JdbcTokenStore(dataSource);

}

@Bean

public AuthorizationCodeServices authorizationCodeServices() {

return new JdbcAuthorizationCodeServices(dataSource);

}

@Bean

public ApprovalStore approvalStore(){

return new JdbcApprovalStore(dataSource);

}

/**

* 配置令牌端点(Token Endpoint)的安全约束.

*

* @param security security

* @throws Exception

*/

@Override

public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

security.realm(REALM);

security.passwordEncoder(passwordEncoder);

security.allowFormAuthenticationForClients();

security.tokenKeyAccess("permitAll()");

security.checkTokenAccess("isAuthenticated()");

}

/**

* 配置客户端详情服务(ClientDetailsService)

* 客户端详情信息在这里进行初始化

* 通过数据库来存储调取详情信息

*

* @param clients clients

* @throws Exception

*/

@Override

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

clients.withClientDetails(clientDetails());

}

/**

* 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)

*

* @param endpoints endpoints

* @throws Exception

*/

@Override

public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

endpoints.authenticationManager(authenticationManager);

endpoints.tokenStore(tokenStore());

endpoints.userDetailsService(userDetailsService);

endpoints.authorizationCodeServices(authorizationCodeServices());

endpoints.approvalStore(approvalStore());

// 为解决获取token并发问题

DefaultTokenServices tokenServices = new TestDefaultTokenServices();

tokenServices.setTokenStore(endpoints.getTokenStore());

tokenServices.setSupportRefreshToken(true);

tokenServices.setClientDetailsService(endpoints.getClientDetailsService());

tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());

endpoints.tokenServices(tokenServices);

}

}}

基于JbdcToken,并发操作时会抛异常,加锁解决@Override

public synchronized OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

return super.createAccessToken(authentication);

}

@Override

public synchronized OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) {

return super.refreshAccessToken(refreshTokenValue, tokenRequest);

}

因为我这边配置tokenStore方式,是通过dubbo远程RPC暴露(终端控制),因此要配置分布式锁try{

lock = redisTemplate.opsForValue().setIfAbsent(lockKey, LOCK);

logger.info("是否获取到锁:"+lock);

if (lock) {

// TODO

super.createAccessToken(authentication);

}else {

logger.info("没有获取到锁!");

}

}finally{

redisTemplate.delete(lockKey);

logger.info("cancelCouponCode任务结束,释放锁!");

}

3. OAuth2AuthenticationProcessingFilter  核心过滤器

OAuth2受保护资源的预认证过滤器。 从传入请求中提取一个OAuth2令牌,并使用它来使用{@link OAuth2Authentication}(如果与OAuth2AuthenticationManager一起使用)填充Spring Security上下文。public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,

ServletException {

final boolean debug = logger.isDebugEnabled();

final HttpServletRequest request = (HttpServletRequest) req;

final HttpServletResponse response = (HttpServletResponse) res;

try {

Authentication authentication = tokenExtractor.extract(request);

if (authentication == null) {

if (stateless && isAuthenticated()) {

if (debug) {

logger.debug("Clearing security context.");

}

SecurityContextHolder.clearContext();

}

if (debug) {

logger.debug("No token in request, will continue chain.");

}

}

else {

request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());

if (authentication instanceof AbstractAuthenticationToken) {

AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;

needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));

}

//身份认证

Authentication authResult = authenticationManager.authenticate(authentication);

if (debug) {

logger.debug("Authentication success: " + authResult);

}

//成功事件通知

eventPublisher.publishAuthenticationSuccess(authResult);

//保存Security上下文

SecurityContextHolder.getContext().setAuthentication(authResult);

}

}

catch (OAuth2Exception failed) {

SecurityContextHolder.clearContext();

if (debug) {

logger.debug("Authentication request failed: " + failed);

}

eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),

new PreAuthenticatedAuthenticationToken("access-token", "N/A"));

authenticationEntryPoint.commence(request, response,

new InsufficientAuthenticationException(failed.getMessage(), failed));

return;

}

chain.doFilter(request, response);

}

4.OAuth2AuthenticationManager 认证管理

在上一节源码中有提到,和它的实现类 ProviderManager  (未携带access_token)

这节认证时候携带 access_token  则跳转 OAuth2AuthenticationManager

核心源码:public Authentication authenticate(Authentication authentication) throws AuthenticationException {

if (authentication == null) {

throw new InvalidTokenException("Invalid token (token not found)");

}

String token = (String) authentication.getPrincipal();

OAuth2Authentication auth = tokenServices.loadAuthentication(token);

if (auth == null) {

throw new InvalidTokenException("Invalid token: " + token);

}

Collection resourceIds = auth.getOAuth2Request().getResourceIds();

if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {

throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");

}

checkClientDetails(auth);

if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {

OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();

// Guard against a cached copy of the same details

if (!details.equals(auth.getDetails())) {

// Preserve the authentication details from the one loaded by token services

details.setDecodedDetails(auth.getDetails());

}

}

auth.setDetails(authentication.getDetails());

auth.setAuthenticated(true);

return auth;

}

这边的 tokenServices  是资源服务器的 tokenServices  和 上一节的 认证服务器 tokenServices是两个独立的service

认证服务器public interface AuthorizationServerTokenServices {

//创建token

OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;

//刷新token

OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)

throws AuthenticationException;

//获取token

OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

}

资源服务器public interface ResourceServerTokenServices {

//根据accessToken加载客户端信息

OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;

//根据accessToken获取完整的访问令牌详细信息。

OAuth2AccessToken readAccessToken(String accessToken);

}

5.了解@Configuration

public class WebMvcConfig extends WebMvcConfigurerAdapter {

@Bean

public LocaleResolver localeResolver() {

SessionLocaleResolver slr = new SessionLocaleResolver();

// 默认语言

slr.setDefaultLocale(Locale.US);

return slr;

}

@Bean

public LocaleChangeInterceptor localeChangeInterceptor() {

LocaleChangeInterceptor lci = new LocaleChangeInterceptor();

// 参数名

lci.setParamName("lang");

return lci;

}

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(localeChangeInterceptor());

}

@Override

public void addViewControllers(ViewControllerRegistry registry) {

registry.addViewController("/login").setViewName("login");

registry.addViewController("/oauth/confirm_access").setViewName("authorize");

}

}

@Configuration

@EnableWebSecurity

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired

private UserDetailsService userDetailsService;

@Bean

public PasswordEncoder passwordEncoder() {

return new PasswordEncoder();

}

@Bean

@Override

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http

// 头部缓存

.headers()

.cacheControl()

.and()

// 防止网站被人嵌套

.frameOptions()

.sameOrigin()

.and()

.csrf()

.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

.and()

// 跨域支持

.cors();

http

.requestMatchers()

//接受的请求

.antMatchers("/login", "/logout", "/oauth/authorize", "/oauth/confirm_access")

.and()

.authorizeRequests()// 端点排除

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage("/login")

.failureUrl("/login?error")

.permitAll()

.and()

.logout()

.logoutUrl("/logout")

.invalidateHttpSession(true).clearAuthentication(true);

}

}

endPoint包下提供许多http接口

CheckTokenEndpoint@RequestMapping(value = "/oauth/check_token")

@ResponseBody

public Map checkToken(@RequestParam("token") String value) {

OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);

if (token == null) {

throw new InvalidTokenException("Token was not recognised");

}

if (token.isExpired()) {

throw new InvalidTokenException("Token has expired");

}

OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());

Map response = accessTokenConverter.convertAccessToken(token, authentication);

return response;

}

补充

涉及到一些设计模式:

oAuth2RequestFactory                      工厂模式

ResourceServerConfigurerAdapter    适配器模式

AbstractConfiguredSecurityBuilder    建造者模式

TokenStore                                         模板方法模式

AuthenticationEventPublisher            发布订阅模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值