Spring Oauth2

0

最近在看Spring Oauth2的东西,开始看的非常迷糊,后来才有点点找到门路。

先看下现在的Spring Oauth2依赖:

spring-security-oauth2
spring-cloud-starter-oauth2
spring-security-oauth2-client
spring-security-oauth2-resource-server
spring-security-oauth2-authorization-server
spring-boot-starter-oauth2-client
spring-boot-starter-oauth2-resource-server

兄弟们是不是非常乱,spring-security-oauth2已经标注废弃。
spring-cloud-starter-oauth2网上代码基本上都是通过这个实现的,基于spring-security-oauth2实现,所以废弃应该是迟早的事情。

官方现在推荐使用的应该是spring-security-oauth2开头的这些,下面的spring-boot-starter-oauth2对应包含上面spring-security-oauth2依赖。

spring-security-oauth2-autoconfigure

虽然这个已经废弃使用,但是还是记录一下,如果授权服务器和资源服务器是同一个模块,直接使用相同的TokenStore,可以忽略一些配置:

security:
  oauth2:
    client:
      client-id: clientApp
      client-secret: secret
      scope: all
# 使用相同TokenStore不用配置Token校验(如果不同可以换为JWT和JWK配置提高性能)
#   resource:
#     token-info-uri: https://localhost:8082/oauth/check_token

认证服务器配置

/**
 * 认证
 * 
 * @author yusheng
 */
@Configuration
@SuppressWarnings("deprecation")
@EnableAuthorizationServer
public class AuthorizationServerAutoConfiguration extends AuthorizationServerConfigurerAdapter {

	@Value("${camera.oauth.jwt.file:}")
	private String jwtFile;
	@Value("${camera.oauth.jwt.alias:}")
	private String jwtAlias;
	@Value("${camera.oauth.jwt.password:}")
	private String jwtPassword;
	@Value("${camera.oauth.jwt.access.days:30}")
	private int jwtAccessDays;
	@Value("${camera.oauth.jwt.refresh.days:180}")
	private int jwtRefreshDays;
	
	@Autowired
	private DataSource dataSource;
	@Autowired
	private TokenStore tokenStore;
	@Autowired
	public PasswordEncoder passwordEncoder;
	@Autowired(required = false)
	private OAuth2RequestFactory requestFactory;
	@Autowired
	public UserDetailsService userDetailsService;
	@Autowired
	private ClientDetailsService clientDetailsService;
	@Autowired
	private AuthenticationManager authenticationManager;
	@Autowired(required = false)
	private AuthorizationCodeServices authorizationCodeServices;

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.jdbc(this.dataSource);
	}
	
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security
			.allowFormAuthenticationForClients()
			.tokenKeyAccess("isAuthenticated()")
			.checkTokenAccess("isAuthenticated()");
	}
	
	@Override
	public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		OAuth2RequestFactory oAuth2RequestFactory = this.requestFactory;
		if(oAuth2RequestFactory == null) {
			oAuth2RequestFactory = new DefaultOAuth2RequestFactory(this.clientDetailsService);
		}
		AuthorizationCodeServices authorizationCodeServices = this.authorizationCodeServices;
		if(authorizationCodeServices == null) {
			authorizationCodeServices = new InMemoryAuthorizationCodeServices();
		}
		final AuthorizationServerTokenServices authorizationServerTokenServices = this.tokenService();
		final List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
		tokenGranters.add(new RefreshTokenGranter(authorizationServerTokenServices, this.clientDetailsService, oAuth2RequestFactory));
//		tokenGranters.add(new ImplicitTokenGranter(authorizationServerTokenServices, this.clientDetailsService, requestFactory));
//		tokenGranters.add(new ClientCredentialsTokenGranter(authorizationServerTokenServices, this.clientDetailsService, requestFactory));
		tokenGranters.add(new AuthorizationCodeTokenGranter(authorizationServerTokenServices, authorizationCodeServices, this.clientDetailsService, oAuth2RequestFactory));
		tokenGranters.add(new ResourceOwnerPasswordTokenGranter(this.authenticationManager, authorizationServerTokenServices, this.clientDetailsService, oAuth2RequestFactory));
		endpoints
			.tokenGranter(new CompositeTokenGranter(tokenGranters))
			.tokenServices(authorizationServerTokenServices)
			.userDetailsService(this.userDetailsService)
			.exceptionTranslator(new MessageCodeTranslator())
			.authenticationManager(this.authenticationManager);
	}
	
	private AuthorizationServerTokenServices tokenService() {
		final DefaultTokenServices tokenServices = new DefaultTokenServices();
		tokenServices.setTokenStore(this.tokenStore);
		tokenServices.setSupportRefreshToken(true);
		tokenServices.setTokenEnhancer(this.jwtAccessTokenConverter());
		tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(this.jwtAccessDays));
		tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(this.jwtRefreshDays));
		return tokenServices;
	}
	
	@Bean
	@ConditionalOnMissingBean
	public TokenKeyEndpoint tokenKeyEndpoint() {
	    return new TokenKeyEndpoint(this.jwtAccessTokenConverter());
	}

	@Bean
	@ConditionalOnMissingBean
	public TokenStore tokenStore() {
		return new JwtTokenStore(this.jwtAccessTokenConverter());
	}

	private JwtAccessTokenConverter jwtAccessTokenConverter() {
		final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
			new ClassPathResource(this.jwtFile),
			this.jwtPassword.toCharArray()
		);
		// Token增强
		final JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
			@Override
			public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
				final Object principal = authentication.getPrincipal();
				if (principal instanceof Principal) {
					final Principal target = (Principal) principal;
					final Map<String, Object> info = new HashMap<>();
					info.put("id", target.getId());
					info.put("user_name_cn", target.getUsernameCn());
					((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
				}
				return super.enhance(accessToken, authentication);
			}
		};
		converter.setKeyPair(keyStoreKeyFactory.getKeyPair(this.jwtAlias));
		return converter;
	}
	
	@Bean
	@ConditionalOnMissingBean
	public DaoAuthenticationProvider daoAuthenticationProvider() throws Exception {
		final DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
		daoAuthenticationProvider.setPasswordEncoder(this.passwordEncoder);
		daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
		daoAuthenticationProvider.afterPropertiesSet();
		return daoAuthenticationProvider;
	}
	
}

资源服务器配置

/**
 * 资源
 * 
 * @author yusheng
 */
@Configuration
@SuppressWarnings("deprecation")
@EnableWebSecurity
@ConditionalOnClass(EnableResourceServer.class)
@EnableResourceServer
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {

	@Value("#{'${camera.permit.url:}'.split(',')}")
	private String[] permitUrl;
	
	@Autowired
	private TokenStore tokenStore;
	
	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
		// 错误
		resources
		.stateless(true)
		.tokenStore(this.tokenStore)
		.authenticationEntryPoint(new AuthenticationEntryPoint() {
			@Override
			public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//				throw MessageCodeException.of(authException, MessageCode.CODE_3401, "没有授权");
				response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
				response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
				response.getWriter().write(Message.fail(MessageCode.CODE_3401, "没有授权").toString());
			}
		});
	}
	
	@Override
	public void configure(HttpSecurity security) throws Exception {
		security
			.csrf().disable()
			.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
			.and()
			.authorizeRequests()
			.antMatchers(
				// 错误
				"/error",
				// 图标
				"/favicon.ico",
				// swagger
				"/v3/api-docs", "/swagger-ui/**", "/swagger-resources/**"
			).permitAll()
			// 配置允许URL
			.antMatchers(this.permitUrl).permitAll()
			.anyRequest().authenticated();
	}
	
}