# Spring Cloud OAuth2 从零开始实现用户认证和单点登录
# OAuth2 是什么
OAuth2 其实是一个关于授权的网络标准,它制定了设计思路和运行流程,利用这个标准我们其实是可以自己实现 OAuth2 的认证过程的。 spring-cloud-starter-oauth2 是 Spring Cloud 按照 OAuth2 的标准并结合 spring-security 封装好的一个具体实现。
OAuth 2 有四种授权模式,分别是授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials),具体 OAuth2 是什么,可以参考这篇文章(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html )。
# OAuth2 的使用场景
- 典型的 OAuth2 使用场景:微信登录、QQ 登录、微博登录、Google 帐号登录、Github 帐号登录等。第一次使用就无需注册,直接通过第三方平台授权登录即可,大大提高了使用效率。此外,服务不需要存储用户的密码,只需要存储认证平台返回的唯一 ID 和用户信息即可。
- 不使用 OAuth2 的场景:用户需要先完成注册,然后用注册号的帐号密码或者用手机验证码登录。
# OAuth2 实现统一认证功能
# 创建并配置认证服务端 auth-server
认证服务端负责验证帐号、密码、存储 Token、检查 Token、刷新 Token 等。
# 1、引入需要的 Maven 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2</artifactId>
</dependency>
# 2、配置 bootstrap.yml 和 Nacos 配置
认证服务器采用 Nacos Config 方案,将配置放在 Nacos 注册中心上
- bootstrap.yml 配置
spring:
application:
name: auth-server
cloud:
nacos:
config:
prefix: auth-server-config
server-addr: xxxx
file-extension: yaml
group: refactored-spring-cloud
- auth-server-config 配置
server:
port: 18003
spring:
datasource:
url: jdbc:mysql://xxxx:3306/spring?useUnicode=true&&characterEncoding=UTF-8&&serverTimezone=Asia/Shanghai
username: xxxx
password: xxxx
jpa:
show-sql: true
generate-ddl: true
database-platform: org.hibernate.dialect.MYSQL5InnoDBDialect
database: mysql
application:
name: auth-server
cloud:
nacos:
discovery:
server-addr: xxxx:8848
group: refactored-spring-cloud
inetutils:
ignored-interfaces: eth.*
preferred-networks: xxxx
redis:
host: xxxx
port: 6379
management:
endpoint:
health:
enabled: true
dubbo:
protocol:
name: dubbo
port: -1
registry:
address: spring-cloud://xxxx
consumer:
timeout: 3000
# 3、配置 Spring Security
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 开放所有接口
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**")
.permitAll();
}
}
- PasswordEncoder:采用 BCrypt 加密算法
- AuthenticationManager:OAuth2 密码模式必须制定的授权管理,用默认的即可
- configure:配置拦截器,使用通配符开放所有接口访问权限
# 4、实现 UserDetailsService
@Slf4j
@Commponent(value = "kiteUserDetailService")
public class KiteUserDetailService implements UserDetailService {
@DubboReference
IUserService service;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username is: " + username);
// 查询用户
if (user == null) {
throw new UsernameNotFoundException("The user is not found");
} else {
// 查询角色
List<SysRole> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (SysRole role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
// 查询密码
String password = user.getPassword();
return new User(username, password, authorities);
}
}
}
- loadUserByUsername:首先利用用户微服务接口通过 username 查询用户、角色以及密码,然后返回
org.springframework.security.core.userdetails.User
即可。
# 5、配置 OAuth2
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
public PasswordEncoder passwordEncoder;
@Autowired
public UserDetailsService kiteUserDetailsService;
@Autowired
private TokenStore jwtTokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private DataSource dataSource;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// Redis token 方式
endpoints.authenticaionManager(authenticationManager)
.userDetailsService(kiteUserDetailsService)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenStore(jwtTokenStore);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder builder = clients.jdbc(dataSource);
builder.passwordEncoder(passwordEncoder);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFromAuthenticationForClients();
security.checkTokenAccess("isAuthenticated");
security.tokenKeyAccess("isAuthenticated");
}
}
有三个 configure 方法的重写
AuthorizationServerEndpointConfigurer 参数的重写
- authenticationManager:用于支持 password 模式
- userDetailsService:设置用户验证服务
- tokenStore:制定 token 的存储方式
- accessTokenConverter:开启 json web token 模式
ClientDetailsServiceConfigure 参数的重写:采用数据库配置的方式,预先定义好 oauth2_client_details 表,如下:
参数说明:
- clientId、client_secret:这两个参数对应请求端定义的 cleint-id 和 client-secret
- authorized_grant_types:包括 authorization_code(授权码模式)、password(密码模式)、implicit(隐式授权类型)、client_credentials、refresh_token 这五种中的一种或多种。
- access_token_validity:token 的有效期
- scopes:用来限定客户端访问的权限,只有在 scopes 定义内的,才可以正常换取 token。
create table oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);
AuthorizationServerSecurityConfigurer 参数的重写:限制客户端访问认证接口的权限
- allowFormAuthenticationForClients():允许客户端访问 OAuth2 授权接口,否则返回 401
- checkTokenAccess :允许已授权用户访问 checkToken 接口。
- tokenKeyAccess:允许已授权用户访问获取 token 接口。
# 6、配置 JWTTokenStore
@Configuration
public class JWTTokenStore {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("dev");
return accessTokenConverter;
}
}
# 7、启动 auth-server
现在已经可以访问 OAuth2 相关的 Restful 接口:
- POST /oauth/authorize 授权码模式认证授权接口
- GET/POST /oauth/token 获取 token 的接口
- POST /oauth/check_token 检查 token 合法性接口