以下文章可以辅助理解Oauth2相关概念
Spring Security与OAuth2介绍:https://www.jianshu.com/p/63115c71a590
Spring Security GrantedAuthority(已授予的权限): https://www.cnblogs.com/longfurcat/p/9417422.html
文章目录
1、添加maven依赖
<!-- web项目启动模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Security Oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置 WebSecurity
/**
* ===================================
* 描 述 : 授权配置
* 包 名 : top.qinxq.single.common.auth
* 创建人 : qinxq
* ===================================
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* =====================================
* 描 述 : 这一步的配置是必不可少的,否则没有password grant_type
* 参 数 : []
* 返 回 值 : org.springframework.security.authentication.AuthenticationManager
* 创 建 人 : qinxq
* =====================================
*/
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
3、实现 UserDetailsService 接口
重写 loadUserByUsername 方法。该方法应该返回用户的基本信息,包括权限权限信息,即 UserDetails 对象
/**
* ===================================
* 描 述 : 认证数据访问
* 包 名 : top.qinxq.single.service.impl
* 创建人 : qinxq
* ===================================
*/
@Service("userDetailService")
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private ISysUserService userService;
@Override
public UserDetails loadUserByUsername(String username){
UserVO userVo = userService.findUserByUsername(username);
if(null == userVo || userVo.getUserId()==null){
throw new UsernameNotFoundException("用户不存在");
}
return new UserDetailsImpl(userVo);
}
}
以下请按自己的方式实现,以下仅供参考
本例采用 UserVO 和 RoleVO 作为用户和角色的视图传输对象,并单独实现UserDetails。
UserVO
/**
* ===================================
* 描 述 : 用户角色信息
* 包 名 : top.qinxq.single.entity.vo
* 创建人 : qinxq
* ===================================
*/
@Data
public class UserVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
private Integer userId;
/**
* 用户名
*/
private String username;
/**
* 用户昵称/真实姓名
*/
private String nickName;
/**
* 密码
*/
private String password;
/**
* 随机盐
*/
private String salt;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 0-正常,1-删除,2-锁定
*/
private Integer status;
/**
* 手机
*/
private String phone;
/**
* 角色列表
*/
private List<RoleVO> roleList;
}
RoleVO
/**
* ===================================
* 描 述 : 角色
* 包 名 : top.qinxq.single.entity.vo
* 创建人 : qinxq
* ===================================
*/
@Data
public class RoleVO implements Serializable {
private static final long serialVersionUID = 1L;
private Integer roleId;
private String roleName;
private String roleCode;
private String roleDesc;
private Date createTime;
private Date updateTime;
private String isDel;
}
UserDetailsImpl
/**
* ===================================
* 描 述 : 重写Security的用户认证信息工具类
* 包 名 : top.qinxq.single.entity.vo
* 创建人 : qinxq
* ===================================
*/
@Data
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
private Integer userId;
private String username;
private String password;
private Integer status;
private List<RoleVO> roleList;
public UserDetailsImpl(UserVO userVo) {
this.userId = userVo.getUserId();
this.username = userVo.getUsername();
this.password = userVo.getPassword();
this.status = userVo.getStatus();
roleList = userVo.getRoleList();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorityList = new ArrayList<>();
if(CollectionUtil.isNotEmpty(roleList)){
for (RoleVO role : roleList) {
authorityList.add(new SimpleGrantedAuthority(role.getRoleCode()));
}
}
authorityList.add(new SimpleGrantedAuthority(SecurityConstants.BASE_ROLE));//常量 "ROLE_USER"
return authorityList;
}
@Override
public boolean isAccountNonExpired() { //账号过期
// if(null != this.accountExpired && this.accountExpired.compareTo(new Date()) < 0) {
// return false;
// }
return true;
}
@Override
public boolean isAccountNonLocked() { //账号锁定
return CommonConstant.SYS_USER_STATUS.STATUS_LOCK.ordinal() == status ? false : true;
}
@Override
public boolean isCredentialsNonExpired() { //密码等是否过期
// if(null != this.passwordExpired && this.passwordExpired.compareTo(new Date()) < 0) {
// return false;
// }
return true;
}
@Override
public boolean isEnabled() {
return CommonConstant.SYS_USER_STATUS.STATUS_DEL.ordinal() == status ? false : true;
}
}
4、配置资源服务器
/**
* ===================================
* 描 述 : 资源服务器
* 包 名 : top.qinxq.single.common.auth
* 创建人 : qinxq
* ===================================
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// //表单登录 方式
// http.formLogin()
// .loginPage("/authentication/require")
// //登录需要经过的url请求
// .loginProcessingUrl("/authentication/form");
http
.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.anyRequest()
.authenticated()
.and()
//关闭跨站请求防护
.csrf().disable();
}
}
5、配置认证服务器
/**
* ===================================
* 描 述 : 认证服务器
* 包 名 : top.qinxq.single.common.auth
* 创建人 : qinxq
* ===================================
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserDetailsService userDetailService;
/**
* =====================================
* 描 述 : 配置客户端信息
* 参 数 : [clients]
* 返 回 值 : void
* 创 建 人 : qinxq
* =====================================
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//这里直接把配置信息保存在内存中
clients.inMemory()
.withClient("app")
//这里必须使用加密
.secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("app"))
// 配置 GrantTypes 支持 刷新token 使用密码模式
.authorizedGrantTypes("client_credentials","refresh_token","password")
//服务域
.scopes("server");
}
/**
* =====================================
* 描 述 : 配置 Token 的节点 和 Token 服务
* 参 数 : [endpoints]
* 返 回 值 : void
* 创 建 人 : qinxq
* =====================================
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailService);
}
/**
* =====================================
* 描 述 : token 使用内存形式
* 参 数 : []
* 返 回 值 : org.springframework.security.oauth2.provider.token.TokenStore
* 创 建 人 : qinxq
* =====================================
*/
@Bean
public TokenStore tokenStore(){
return new InMemoryTokenStore();
}
/**
* =====================================
* 描 述 : 密码加密方式
* 参 数 : []
* 返 回 值 : org.springframework.security.crypto.password.PasswordEncoder
* 创 建 人 : qinxq
* =====================================
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
6、测试
获取token(/oauth/token)
-
请求地址: localhost:port/oauth/token
-
请求方法: POST
-
请求头:
请求头中,需要传入一个Authorization 参数
参数值为 Oauth2 配置客户端中的Client与secret以 'Client:secret’的形式组合,然后再进行 Base64 编码
本例为Base64(app:app);
-
请求参数:
- grant_type : password (授权模式)
- username :[用户名]
- password :[密码]
访问受保护的资源
-
请求头: Authorization 值为 bearer + 空格 + token
ps:bearer 为 获取token 时,返回的 token_type 值
相关链接
SpringCloud、SpringBoot2.0 整合Oauth2 (一) 基本配置
SpringCloud、SpringBoot2.0 整合Oauth2 (一) 基本配置