OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0的系统大致分由客户端,认证授权服务器以及资源服务器三部分组成。客户端如果想要访问资源服务器中的资源,就必须要持有认证授权服务器颁发的Token。认证流程如下图所示:
这篇文章将通过一个具体的案例来展示如何搭建一个分布式的OAuth2.0系统。整体的结构图如下所示。有网关,认证授权服务以及资源服务三个部分组成。既然OAuth2是一个标准,如果我们想用的话,必然是用它的实现,也就是Spring-Security-OAuth2,它可以很方便地和Spring Cloud集成。OAuth2.0的更多细节会在案例中继续介绍。
那么就开始吧!
要完成这套系统,需要准备好用到的一些数据表。
建表的sql我放在了源码的README.md文件中,下载地址见文末。
微服务项目得先有个注册中心吧,我们选用Eureka。先搭建一个父工程OAuth2Demo,然后在父工程中创建一个Module叫oauth2_eureka。然后添加配置文件及启动类即可。所需要的依赖我就不在这里贴了,太占篇幅了。有需要的小伙伴直接去我源码中拷就行了。
spring:
application:
name: eureka
server:
port: 8000 #启动端口
…………
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
这样注册中心就搭建好了。
在OAuth2Demo中创建一个Module叫oauth2_uaa作为认证服务。添加启动类和配置文件。
spring.application.name=uaa
server.port=8001
eureka.client.serviceUrl.defaultZone = http://localhost:8000/eureka/
…………
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.robod.uaa.mapper")
public class UaaApplication {
public static void main(String[] args) {
SpringApplication.run(UaaApplication.class, args);
}
}
回顾上一篇Spring Security的文章中提到的几点内容
public interface UserService extends UserDetailsService {
}
//-----------------------------------------------------------
@Service("userService")
public class UserServiceImpl implements UserService {
…………
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = userMapper.findByUsername(username);
return sysUser;
}
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
…………
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
//认证用户的来源
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
//配置SpringSecurity相关信息
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers("/login*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
解释一下上面的代码,WebSecurityConfig是Spring Security的配置类,第一个configure()方法配置的是用户的来源,这里配置了自定义的实现了UserDetailsService接口的UserService,里面的loadUserByUsername()方法从数据库中查询出对应的实现了UserDetails接口的SysUser对象,里面的SysPermission封装了用户所拥有的权限。然后就交给后续的过滤器去处理了,我们就不用去管了。
然后我们就可以去进行OAuth2.0的相关配置了,方法很简单,只要在配置类上添加@EnableAuthorizationServer注解并让其继承自AuthorizationServerConfigurerAdapter。最后重写其中的三个configure()方法即可。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager; //从WebSecurityConfig中获取的
@Autowired
private AuthorizationCodeServices authorizationCodeServices; //本类中的,授权码模式需要
@Autowired
private TokenStore tokenStore; //TokenConfig中的
@Autowired
private PasswordEncoder passwordEncoder;//从WebSecurityConfig中获取的
@Autowired
private ClientDetailsService clientDetailsService; //本类中的
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter; //TokenConfig中的
//用来配置令牌端点的安全约束
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll") // /oauth/token_key 提供公有密匙的端点 允许任何人访问
.checkTokenAccess("permitAll") // /oauth/check_token :用于资源服务访问的令牌解析端点 允许任何人访问
.allowFormAuthenticationForClients(); //表单认证(申请令牌)
}
//用来配置客户端详情服务,客户端详情信息在这里进行初始化,
//你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
//用来配置令牌(token)的访问端点(url)和令牌服务(token services)
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager) //认证管理器,密码模式需要
.authorizationCodeServices(authorizationCodeServices) //授权码服务,授权码模式需要
.tokenServices(tokenService())
.allowedTokenEndpointRequestMethods(HttpMethod.POST); //允许post提交
}
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
//设置授权码模式的授权码存取到数据中
return new JdbcAuthorizationCodeServices(dataSource);
}
//客户端详情服务,从数据库中获取
@Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService)clientDetailsService).setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
//令牌管理服务
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService); //客户端信息服务
service.setSupportRefreshToken(true); //支持自动刷新
service.setTokenStore(tokenStore);
//令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(7200); //令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); //刷新令牌默认有效期3天
return service;
}
}