SpringBoot OAuth2.0 认证授权(密码模式)
SpringBoot 整合 SpringSecurity,token 落地,前后端分离接口安全。
SpringBoot 环境搭建和入门:Spring Boot 2.x 快速入门
导入 mysql 脚本
包含用户表,oauth2.0 数据脚本
https://gitee.com/shizidada/moose-resource/blob/master/moose-security.sql
全部 : https://gitee.com/shizidada/moose/blob/master/src/main/resources/moose.sql
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
创建 WebSecurity 配置文件
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class MooseWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
/**
* 用于支持 password 模式 密码模式需要 AuthenticationManager 支持
* password 模式一点要加上这个
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
创建 AuthorizationServer 配置
/**
* 资源认证配置
* <p>
* Description:
* </p>
*
* @author taohua
* @version v1.0.0
* @see com.moose.operator.web.security.configure
* <p>
* [/oauth/authorize] [/oauth/token] [/oauth/check_token]
* [/oauth/confirm_access] [/oauth/token_key] [/oauth/error]
*/
@Configuration
@EnableAuthorizationServer
public class MooseAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
/**
* 注入用于支持 password 模式
*/
@Resource
private AuthenticationManager authenticationManager;
@Resource
private AccountService accountService;
/**
* 需要加上,避免循环依赖问题
*/
@Lazy
@Resource(name = "userDetailsServiceBean")
private UserDetailsService userDetailsService;
@Resource
private DataSource dataSource;
@Resource
private WebResponseExceptionTranslator customOAuth2ResponseExceptionTranslator;
/**
* 设置用户密码的加密方式
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Token 持久化
*
* @return TokenStore
*/
@Bean
public TokenStore tokenStore() {
// 基于 JDBC 实现,令牌保存到数据库
return new JdbcTokenStore(dataSource);
}
/**
* A service that provides the details about an OAuth2 client.
*
* @return ClientDetailsService
* <p>
* 基于 JDBC 实现,需要事先在数据库配置客户端信息
*/
@Bean
public ClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
/**
* Authorization Server endpoints.
*
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.exceptionTranslator(customOAuth2ResponseExceptionTranslator);
// 用于支持密码模式
endpoints.authenticationManager(authenticationManager);
}
/**
* 授权服务安全配置
*
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.passwordEncoder(passwordEncoder());
/**
* 对于端点可以匿名访问
* [/oauth/authorize] [/oauth/token] [/oauth/check_token]
* [/oauth/confirm_access] [/oauth/token_key] [/oauth/error]
*/
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
/**
* 授权客户端配置
*
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 客户端配置
clients.withClientDetails(jdbcClientDetailsService());
}
}
创建 ResourceServer配置
@Configuration
@EnableResourceServer
public class MooseResourceServerConfiguration extends ResourceServerConfigurerAdapter {
/**
* 保存匿名访问的 url,表示可以不需要权限直接可以访问
*/
private final List<String> anonymousAntPatterns = new ArrayList<>();
@Resource
private MooseAuthenticationFailureHandler mooseAuthenticationFailureHandler;
@Resource
private MooseAccessDeniedHandler mooseAccessDeniedHandler;
@Resource
private MooseAuthenticationEntryPoint mooseAuthenticationEntryPoint;
/**
* 需要加上,避免循环依赖问题
*/
@Lazy
@Resource
private TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
// 对登录注册要允许匿名访问
for (String pattern : anonymousAntPatterns) {
http.authorizeRequests().antMatchers(pattern).permitAll();
}
// 禁用 session
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().anyRequest().authenticated().and()
.cors().and()
.csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// 配置资源 ID,需要在 oauth_client_details 表中添加,详细查看数据库脚本
resources.resourceId("app-resources").stateless(true);
resources.accessDeniedHandler(mooseAccessDeniedHandler);
resources.authenticationEntryPoint(mooseAuthenticationEntryPoint);
}
}
自定义认证 UserDetailsService
- 需要继承 UserDetailsService 类
- 重写 loadUserByUsername 方法,通过这个方法得到访问接口传递的用户名
- 根据用户名进行用户名密码校验和权限校验
- 查询出来的用户信息构建 UserDetails 对应进行返回传递
具体可以查看 https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/service/impl/UserDetailsServiceImpl.java
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private AccountServiceImpl accountService;
@Resource
private PasswordServiceImpl passwordService;
/**
* Spring Security
*
* @param accountName 账号
* @return 是否成功
* @throws UsernameNotFoundException 用户名密码异常
*/
@Override
public UserDetails loadUserByUsername(String accountName)
throws UsernameNotFoundException {
if (StringUtils.isEmpty(accountName)) {
throw new BusinessException(ResultCode.ACCOUNT_IS_EMPTY);
}
AccountDTO accountDTO = accountService.getByAccountName(accountName);
if (ObjectUtils.isEmpty(accountDTO)) {
throw new BusinessException(ResultCode.ACCOUNT_OR_PASSWORD_ERROR);
}
// TODO: 禁用账号如何防止多次请求,访问数据库 ???
PasswordDTO passwordDTO = passwordService.getByAccountId(accountDTO.getAccountId());
if (ObjectUtils.isEmpty(passwordDTO)) {
throw new BusinessException(ResultCode.ACCOUNT_OR_PASSWORD_ERROR);
}
// TODO: 角色、权限集合
List<GrantedAuthority> grantedAuthorities = new ArrayList();
grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
return new MooseUserDetails(accountDTO, passwordDTO, grantedAuthorities);
}
}
自定义验证,需要用到 Bean
-
MooseUserDetails 构建用户和权限信息
- https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/security/component/MooseUserDetails.java
-
MooseAccessDeniedHandler 用来解决认证过的用户访问无权限资源时的异常
- https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/security/component/MooseAccessDeniedHandler.java
-
MooseAuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
- https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/security/component/MooseAuthenticationEntryPoint.java
-
MooseAuthenticationExceptionSerializer 自定义授权错误解析器
- https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/security/component/MooseAuthenticationExceptionSerializer.java
-
MooseAuthenticationFailureHandler 自定义授权失败或错误
- https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/security/component/MooseAuthenticationFailureHandler.java
-
MooseAuthenticationResponseExceptionTranslator 自定义授权错误传输器
- https://gitee.com/shizidada/moose/blob/master/src/main/java/com/moose/operator/web/security/component/MooseAuthenticationResponseExceptionTranslator.java
测试
- 向 oauth_client_details 表中 authorized_grant_types 字段添加 password 模式
- 向数据库添加测试用户, password 需要用 BCryptPasswordEncoder 进行加密
# t_account 表
insert into `t_account`(`account_id`,`account_name`,`status`,`phone`,`create_time`,`update_time`) values ('785919644115668992','江景','1','1537031501','2020-12-08 17:24:04','2020-12-08 17:24:04');
# t_password 表
insert into `t_password`(`password_id`,`account_id`,`password`,`create_time`,`update_time`) values ('785919644115668993','785919644115668992','$2a$10$Ll17vDGG3DoABuy9L026KeqjAasYknMXR3cHmxiTyfDl9dS8RtGbi','2020-12-08 17:24:04','2020-12-08 17:24:04');
代码地址: https://gitee.com/shizidada/moose
关注公众号 「全栈技术部」
,不断学习更多有趣的技术知识。