由于项目的关系,系统需要集成oAuth2.0功能。关于OAuth2.0的概念参考文章理解OAuth 2.0,或者 OAuth 2.0最简向导
我们系统使用的Spring cloud G版本,引入依赖jar包
org.springframework.cloud
spring-cloud-starter-oauth2
OAuth 2.0 定义了四种授权方式
在Spring OAuth2.0中除了定义以上四种授权模式外,还定义一个特殊的授权模式:刷新
四种模式和refresh模式对应的处理类是:
其他一些重要的类:
○ AuthorizationCodeServices:用于“授权码模式( authorization code )”如何保存"授权码",默认有两种实现:
○ TokenStore:存储token的方式,常用有以下几种实现
○ ClientDetailsService:客户端账号管理
○ UserDetailsService:如何加载用户账号
○ 其他
网上的案列基本是使用JdbcClientDetailsService 和 InMemoryClientDetailsService 实现客户端账号。这些都不符合我们的要求。JdbcClientDetailsService 必须按照的标准建立相关的数据库表,我们的已经有自己的账户表,不能使用这些创建的表。
我们首先研究InMemoryClientDetailsService里返回的ClientDetails实例值如下:
我们参考JdbcClientDetailsService 和 InMemoryClientDetailsService代码,实现自定义ClientDetailsService。关键代码如下
@Service
public class MyClientDetailsService implements ClientDetailsService {
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
System.out.println("clientId = " + clientId);
// 实际使用时,这里增加读数据库的操作,根据clientId获取账户信息,并根据下面代码的方式生成BaseClientDetails对象即可
BaseClientDetails baseClientDetails = new BaseClientDetails();
baseClientDetails.setClientId("clientapp");
// secret密码配置从 Spring Security 5.0开始必须以 {加密方式}+加密后的密码 这种格式填写
/*
* 当前版本5新增支持加密方式:
* bcrypt - BCryptPasswordEncoder (Also used for encoding)
* ldap - LdapShaPasswordEncoder
* MD4 - Md4PasswordEncoder
* MD5 - new MessageDigestPasswordEncoder("MD5")
* noop - NoOpPasswordEncoder
* pbkdf2 - Pbkdf2PasswordEncoder
* scrypt - SCryptPasswordEncoder
* SHA-1 - new MessageDigestPasswordEncoder("SHA-1")
* SHA-256 - new MessageDigestPasswordEncoder("SHA-256")
* sha256 - StandardPasswordEncoder
*/
baseClientDetails.setClientSecret("{noop}112233");
// 不可以设置为null
// baseClientDetails.setAutoApproveScopes(null);
List scopeList = new ArrayList<>();
scopeList.add("read_userinfo");
scopeList.add("read_contacts");
baseClientDetails.setScope(scopeList);
baseClientDetails.setResourceIds(null);
List grantTypeList = new ArrayList<>();
grantTypeList.add("client_credentials");
// 运行刷新token
grantTypeList.add("refresh_token");
baseClientDetails.setAuthorizedGrantTypes(grantTypeList);
baseClientDetails.setRegisteredRedirectUri(Sets.newHashSet());
// 不可以设置为null
// baseClientDetails.setAuthorities(null);
// 设置accessToken和refreshToken有效期
baseClientDetails.setAccessTokenValiditySeconds(1000);
baseClientDetails.setRefreshTokenValiditySeconds(1000);
baseClientDetails.setAdditionalInformation(Maps.newHashMap());
return baseClientDetails;
}
}
在OAuth2AuthorizationServer中配置客户端的账户的类
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private MyClientDetailsService myClientDetailsService;
@Autowired
private RedisTokenStore redisTokenStore;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置客户端的账户的类
clients.withClientDetails(myClientDetailsService);
}
测试
curl -X POST "http://10.216.33.211:10808/com-oauth/oauth/token" --user clientapp:112233 -d "grant_type=client_credentials&scope=read_contacts
返回:
{"access_token":"9ba822ce-3c1b-461f-85bb-722e83c30248","token_type":"bearer","expires_in":28068,"scope":"read_contacts"}
默认使用,使用内存用户账号
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user_1").password("123456").authorities("USER")
.and()
.withUser("user_2").password("123456").authorities("USER");
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
}
使用自定义用户账号
我们要实现自己的系统,实现UserDetailsService 接口,根据用户名称,如果验证成功,则返回UserDetails 对象。实际应用将这里增加访问自己用户账号的功能即可。
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("name=" + s);
// 获取用户权限列表
List authorities = new ArrayList();
authorities.add(new SimpleGrantedAuthority("admin"));
authorities.add(new SimpleGrantedAuthority("default"));
if(s.equals("hry")) {
String name = "hry";
String password = "1024";
User user = new User(name, password, true, true, true, true, authorities);
return user;
}else {
String name = "default";
String password = "1024";
User user = new User(name, password, true, true, true, true, authorities);
return user;
}
}
}
@EnableWebSecurity 注解类,并实现WebSecurityConfigurerAdapter 的configure(AuthenticationManagerBuilder auth)设置UserDetailsService 的对象
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(myUserDetailsService);
}
}
token的存储由TokenStore实现。TokenStore:存储token的方式,常用有以下几种实现
我们使用RedisTokenStore实现redis存储
@EnableAuthorizationServer:标记这是权限服务器
@Configuration
// enables the Authorization server configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private RedisTokenStore redisTokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
// 默认情况配置在缓存中,如果配置这个,则存储在redis中
endpoints.tokenStore(redisTokenStore);
}
}
初始化RedisTokenStore
@Bean
public RedisTokenStore createRedisTokenStore(RedisConnectionFactory connectionFactory){
return new RedisTokenStore(connectionFactory);
}