OAuth2.0最直观配置

OAuth2.0基础认识可以看这里

spring全家桶已经实现了OAuth2.0的全部功能(spring-cloud-starter-security、spring-security-oauth2-autoconfigure),我们只要引用对应库就可以了。

表结构

无论哪种认证方式,总要把数据存储起来(无论是在缓存还是DB),OAuth2.0依赖的数据一般是存放在DB中的,全部表结构可以参考这里

其中不能省的表就这一个:oauth_client_details(客户端信息配置,当然了表名可以自己随便改)

字段
client_id 客户端唯一标识,有的场景叫AppKey,一个东西
resource_ids 客户端能访问的资源ID集合,一般不用
client_secret 客户端秘钥,这个很重要,不能泄露;有的场景叫AppSecret
scope 客户端申请的权限范围,完全自定义的字符串
authorized_grant_types OAuth2.0支持的授权类型,可选值包括 authorization_code,password,refresh_token,implicit,client_credentials,支持多个 grant_type 用英文逗号分隔
web_server_redirect_uri 客户端的重定向URI, 可为空。用来验证发起请求的参数与数据库中的配置是否一致
authorities 客户端所拥有的 Spring Security 的权限,一般不用
access_token_validity 客户端的 access_token 的有效时间值 (单位:秒), 可选,若不设定值则使用默认的有效时间值 (60 * 60 * 12, 12 小时)
refresh_token_validity 客户端的 refresh_token 的有效时间值 (单位:秒), 可选,若不设定值则使用默认的有效时间值 (60 * 60 * 24 * 30, 30 天)
additional_information 预留的字段,在 Oauth 的流程中没有实际的使用
autoapprove 是否自动 Approval 操作,默认值为 false。只适用于 grant_type=“authorization_code” 的情况,当用户登录成功后,若该值为 true 或支持的 scope 值,则会跳过用户 Approve 的页面,直接授权

其它用户表、角色权限表、token存储表等等都可以用我们自己的业务表来做,token可以存放在redis中。

数据结构

用户信息

org.springframework.security.core.userdetails.User

private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;

这个类可以自定义,继承User就好了,一般都是用项目中的用户类。

权限信息

可以是菜单权限,也可以数据权限,这个完全根据业务来自定义。

比如使用业务系统的角色+权限关系表,就可以知道当前用户能操作哪些菜单/数据(Set authorities)。

加密方式(针对password模式)

认证过程中提交的密码不能是明文,如果不用默认的加密方式,可以自定义(必须与数据库中存储密码的加密方式匹配)。

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

自定义授权服务器配置

@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
	private final DataSource dataSource;
	private final UserDetailsService userDetailsService;
	private final AuthenticationManager authenticationManager;
    // 用户信息和token存放在redis中
	private final RedisConnectionFactory redisConnectionFactory;

	@Override
	@SneakyThrows
	public void configure(ClientDetailsServiceConfigurer clients) {
        // 自定义用户查询实现类
		MyClientDetailsService clientDetailsService = new MyClientDetailsService(dataSource);
        // 自定义查询oauth_client_details数据的sql(根据client_id查询)
		clientDetailsService.setSelectClientDetailsSql("select * from ...");
        // 自定义查询所有oauth_client_details数据的sql
		clientDetailsService.setFindClientDetailsSql("select * from ...");
		clients.withClientDetails(clientDetailsService);
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
		oauthServer
             // 让 /oauth/token 支持 client_id 以及 client_secret 作登录认证
			.allowFormAuthenticationForClients()
             // 允许可访问 /oauth/check_token 对应的api
			.checkTokenAccess("permitAll()");
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
		endpoints
             // oauth请求使用get或put方式
			.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
             // token存储方式
			.tokenStore(tokenStore())
             // 自定义token中附加信息的内容
			.tokenEnhancer(tokenEnhancer())
             // 获取用户信息的实现类
			.userDetailsService(userDetailsService)
			.authenticationManager(authenticationManager)
             // 是否重复使用refreshToken直到过期
			.reuseRefreshTokens(false)
             // 自定义授权确认api
			.pathMapping("/oauth/confirm_access", "/token/confirm_access")
             // 自定义异常处理类
			.exceptionTranslator(new MyWebResponseExceptionTranslator());
	}

	@Bean
	public TokenStore tokenStore() {
		RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
		tokenStore.setPrefix("myOAuth2.0");
		return tokenStore;
	}

	@Bean
	public TokenEnhancer tokenEnhancer() {
		return (accessToken, authentication) -> {
			final Map<String, Object> additionalInfo = new HashMap<>();
			additionalInfo.put("addInfo", "额外信息");
			((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
			return accessToken;
		};
	}
}

授权服务器正确分配了token后,在后续的请求过程中,资源服务器怎么从token上获取用户信息呢?这就需要在资源服务器中配置了。

自定义资源服务器配置

public class MyResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
    // 自定义异常处理类
	@Autowired
	protected ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;
    // 验证token是否有效,默认即可,无需自定义
	@Autowired
	protected RemoteTokenServices remoteTokenServices;
    // 自定义拒绝授权处理器
	@Autowired
	private AccessDeniedHandler myAccessDeniedHandler;
    // 允许匿名访问的资源,支持ant通配符
	@Autowired
	private List<String> permitUrls;
    // rest请求模板,默认即可
	@Autowired
	private RestTemplate restTemplate;

	@Override
	@SneakyThrows
	public void configure(HttpSecurity httpSecurity) {
		ExpressionUrlAuthorizationConfigurer<HttpSecurity>
			.ExpressionInterceptUrlRegistry registry = httpSecurity
			.authorizeRequests();
        // 可匿名访问资源
		permitUrls
			.forEach(url -> registry.antMatchers(url).permitAll());
        // 所有页面需要授权
		registry.anyRequest().authenticated()
			.and().csrf().disable();
	}

	@Override
	public void configure(ResourceServerSecurityConfigurer resources) {
		DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        // 关键位置,根据check_token返回的结果,转换为资源服务器中可用的用户信息
		UserAuthenticationConverter userTokenConverter = new MyUserAuthenticationConverter();
		accessTokenConverter.setUserTokenConverter(userTokenConverter);

		remoteTokenServices.setRestTemplate(restTemplate);
		remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
		resources.authenticationEntryPoint(resourceAuthExceptionEntryPoint)
			.accessDeniedHandler(myAccessDeniedHandler)
			.tokenServices(remoteTokenServices);
	}
}

这里的 UserAuthenticationConverter 会根据 check_token 返回的内容来生成一个org.springframework.security.core.userdetails.User 并把它放在 org.springframework.security.authentication.UsernamePasswordAuthenticationToken 中,这样后面的处理逻辑都能从权限管理器中获取到用户信息了。

那 check_token 是怎么获取的 token 信息呢?

之前我们不是配置了token在redis中存储么?

@Bean
public TokenStore tokenStore() {
    RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
    tokenStore.setPrefix("myOAuth2.0");
    return tokenStore;
}

在 RedisTokenStore 中你会看到是如何反序列化出来的

@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
    return readAuthenticationForRefreshToken(token.getValue());
}

public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
    RedisConnection conn = getConnection();
    try {
        byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));
        OAuth2Authentication auth = deserializeAuthentication(bytes);
        return auth;
    } finally {
        conn.close();
    }
}

redis中存储的内容如下,其实就是一个序列化的 org.springframework.security.oauth2.provider.OAuth2Authentication对象

OAuth2.0最直观配置_第1张图片

到这里,Oauth2.0的配置就很清楚了吧,后面我们再把整个请求过程串起来,就更直观了!

你可能感兴趣的:(JAVA)