- 2.4.2 Web 安全的配置
第二步:
">2.5 获取token测试
第一步:
第二步:- 一、JWT接入
- 一、JWT的登出问题
还没有接入到我们的系统的用户数据,本节课我们来接入一下我们的用户数据">一、授权服务器的接入
之前我们仅仅在authorizaiton-server 里面添加了一个模拟的用户:
还没有接入到我们的系统的用户数据,本节课我们来接入一下我们的用户数据- 5.1 添加登录常量
- 5.2 实现UserDetailService 接口
- 5.3 管理员用户的登录
5.3.2 依赖导入">5.3.1 RBAC模型
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
5.3.2 依赖导入- 5.3.3 配置数据源
- 5.3.4 准备SQL语句
- 5.3.5 代码实现
- 5.3.6 测试效果
- **修改:WebSecurityConfig:将之前的PasswordEncoder 修改为以下的代码:
">5.3.7 密码加密器
**修改:WebSecurityConfig:将之前的PasswordEncoder 修改为以下的代码: - 5.3.8 测试密码匹配器的效果
- 5.4 会员登录的接入
- 一、refresh_token和过期时间
- 一、Token传递和获取
授权服务器的配置
| @EnableAuthorizationServer
@Configuration
public class AuthorizationConfig extends horizationServerConfigurerAdapter {
@Autowired<br /> public PasswordEncoder passwordEncoder ;
@Autowired<br /> private AuthenticationManager authenticationManager ;
@Autowired<br /> private UserDetailsService userDetailsService ;<br /> _/**<br /> * 配置第三方客户端<br /> */<br /> _@Override<br /> public void configure(ClientDetailsServiceConfigurer clients) throws Exception {<br /> clients.inMemory()<br /> .withClient("coin-api")<br /> .secret(passwordEncoder.encode("coin-secret"))<br /> .scopes("all")<br /> .authorizedGrantTypes("password","refresh")<br /> .accessTokenValiditySeconds(24 * 7200)<br /> .refreshTokenValiditySeconds(7 * 24 * 7200) ;<br /> }
_/**<br /> * 设置授权管理器和UserDetailsService<br /> * @param endpoints<br /> * @throws Exception<br /> */<br /> _@Override<br /> public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {<br /> endpoints.tokenStore(new InMemoryTokenStore())<br /> .authenticationManager(authenticationManager)<br /> .userDetailsService(userDetailsService) ;<br /> }<br />} |
| —- |
2.4.2 Web 安全的配置
| @Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
_/**_ * 注入一个验证管理器_ *_ * @return_ * @throws Exception_ */_ _@Bean<br /> public AuthenticationManager authenticationManagerBean() throws Exception {<br /> return super.authenticationManagerBean();<br /> }
_/**_ * 资源的放行_ */_ _@Override<br /> protected void configure(HttpSecurity http) throws Exception {<br /> http.csrf().disable(); _// 关闭scrf_ _http.authorizeRequests().anyRequest().authenticated();<br /> }
_/**_ * 创建一个测试的UserDetail_ * @return_ */_ _@Bean<br /> public UserDetailsService userDetailsService() {<br /> InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();<br /> User user = new User("admin", "123456", Arrays._asList_(new SimpleGrantedAuthority("ROLE_ADMIN"))) ;<br /> inMemoryUserDetailsManager.createUser(user);<br /> return inMemoryUserDetailsManager;<br /> }
_/**_ * 注入密码的验证管理器_ * @return_ */_ _@Bean<br /> public PasswordEncoder passwordEncoder() {<br /> return NoOpPasswordEncoder._getInstance_();<br /> }<br />} |
| —- |
2.5 获取token测试
第一步:
第二步:
一、JWT接入
3.1 Token共享问题
我的token 目前存储在内存里面:
也就是说,当我们仅仅只有一台authorization-server 时,没有任何问题,但是当我们使用多台authorization-server时,由于内存数据无法共享,故用户登录的数据仅仅保存在一台服务器里面,这就会导致某台授权服务器会误判“是否用户登录”这个问题。
使用Redis 共享Token
将之前数据存储在内存里面的问题解决掉,现在直接把token 存储在内存里面:
3.2.1 添加依赖
_ |
---|
3.2.2 添加配置文件
server: port: 9999 spring: application: name: authorization-server cloud: nacos: discovery: server-addr: nacos-server:8848 redis: host: 191.168.19.128 port: 8001 password: 11111 |
---|
3.2.3 使用RedisTokenStore
修改我们之前的AuthorizationServerConfig配置类:
| @EnableAuthorizationServer _// 开启授权服务器的功能@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired<br /> private PasswordEncoder passwordEncoder ;
@Autowired<br /> private AuthenticationManager authenticationManager ;
@Autowired<br /> private UserDetailsService userDetailsService ;
@Autowired private RedisConnectionFactory redisConnectionFactory ;**
_/**_ * 添加第三方的客户端_ */_ _@Override<br /> public void configure(ClientDetailsServiceConfigurer clients) throws Exception {<br /> clients.inMemory()<br /> .withClient("coin-api") _// 第三方客户端的名称_ _.secret(passwordEncoder.encode("coin-secret")) _// 第三方客户端的密钥_ _.scopes("all") _//第三方客户端的授权范围_ _.accessTokenValiditySeconds(3600) _// token的有效期_ _.refreshTokenValiditySeconds(7*3600);_// refresh_token的有效期_ _super.configure(clients);<br /> }
_/**_ * 配置验证管理器,UserdetailService_ */_ _@Override<br /> public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {<br /> endpoints.authenticationManager(authenticationManager)<br /> .userDetailsService(userDetailsService)<br /> **.tokenStore(redisTokenStore());**<br /> super.configure(endpoints);<br /> }
public TokenStore redisTokenStore(){ return new RedisTokenStore(redisConnectionFactory) ; }
} |
| —- |
3.2.4 获取Token 测试
重启authorization-server,获取token测试
3.2.5 观察Redis数据
我们发现,在redis 里面已经保存了用户登录的数据了。
资源服务器和授权服务的交互
**
3.3.1 在授权服务器里面准备userinfo的接口
| @RestController
public class UserInfoController {
_/**_ * 获取该用户的对象_ * @param principal_ * @return_ */_ _@GetMapping("/user/info")<br /> public Principal usrInfo(Principal principal){ _// 此处的principal 由OAuth2.0 框架自动注入_ // 原理就是:利用Context概念,将授权用户放在线程里面,利用ThreadLocal来获取当前的用户对象_// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();_ _return principal ;<br /> }<br />} |
| —- |
3.3.2 将该授权服务器变成资源服务器
因为授权服务器里面提供了userinfo 该资源,所以我们也将它认为是授权服务器
添加配置类就可以了。
@EnableResourceServer @Configuration public class ResourceServerConfig extends ResourceServerConfigurerAdapter { } |
---|
3.3.2使用token 换取user对象
第二步:使用Token 换用户对象
我们可以看见,user 对象已经被获取到了。
3.3.3 存在的问题**
我们在理解资源服务器和授权服务的交互后会发现,授权服务器有巨大的压力:当用户访问每一个受保护的资源时(无论该资源散落在那个微服务里面),资源服务器都要和授权服务器交互一次,这样,授权服务器的压力将非常的大。我们必须找解决方案。
Ø 方案一:使用负载均衡的概念,多部署几台授权服务器
Ø 方案二:让资源服务器不再访问授权服务器
3.4 使用Jwt来做token的存储
上面的方案里面,我们提到了让资源服务器不再访问授权服务器,那会存在什么问题呢?
资源服务器访问授权服务的本质在于2点:
第一点:资源服务器无法验证token的正确性,因为它没有存储token
第二点:资源服务要通过授权服务器来换取用户(token 换 user)。
我们来推演:资源服务器当前只能得到用户给他的token,我们能做的改造有限:
第一步:若我们将用户的基本信息存储在token 里面呢?
第二步:定义一种加密规则,让资源服务器也能去判断该token的正确性。
这样,我们的JWT就上场了。看看JWT的定义:
3.4.1 生成私钥和公钥
生成私钥:
keytool -genkeypair -alias coinexchange -keyalg RSA -keypass coinexchange -keystore coinexchange.jks -validity 365 -storepass coinexchange |
---|
具体命令和参数:
Keytool 是一个java提供的证书管理工具
该文件里面保存的就是私钥信息
解析公钥:
keytool -list -rfc —keystore coinexchange.jks | openssl x509 -inform pem -pubkey |
---|
将解析出来的公钥放在一个文件的文件里面:
——-BEGIN PUBLIC KEY——- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArbzkbzTuolRUWzdGUfj/ cc5BHuQeTXUJuvfHtEFQf5yl2ZZ9Q6banG5Bb9ph9/v5C1BjeoJYtzJoiMfHUOFs BLIYwseII4pt38OQJ4SVu1okOEPv+mgbNxHdyfX0etROCKKFBQrvV+N21IO/meRJ YlXylmWt4/wh78G3jgXFsnCr/VAUqRGxDPA+r3zAXNAFXAiJFEOzvBq+8+QLQ/hv lzN2asfr0M4b/N1mgO6N3atpat3updLD0zzOZ0P8vDhJzNCgPTQe5urxoSg8BH1M BIH8Qx3Mfwq5Lf+SZjCWKzRZpw047MH3ReEER4E0s1F0mmS5MEMWsjrlzzTzY+T7 ewIDAQAB ——-END PUBLIC KEY——- |
---|
保存好,以后备用。
3.4.2 修改配置文件
server: port: 9999 spring: application: name: authorization-server cloud: nacos: discovery: server-addr: nacos-server:8848 |
---|
将之前的redis配置删除就可以了
3.4.3 将私钥文件复制到resource下
3.4.4 修改配置类载入私钥文件
| @EnableAuthorizationServer _// 开启授权服务器的功能@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired<br /> private PasswordEncoder passwordEncoder ;
@Autowired<br /> private AuthenticationManager authenticationManager ;
@Autowired<br /> private UserDetailsService userDetailsService ;
// @Autowired// private RedisConnectionFactory redisConnectionFactory ;__ /** 添加第三方的客户端_ / @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(“coin-api”) // 第三方客户端的名称 .secret(passwordEncoder.encode(“coin-secret”)) // 第三方客户端的密钥 .scopes(“all”) //第三方客户端的授权范围 .accessTokenValiditySeconds(3600) // token的有效期 .refreshTokenValiditySeconds(7*3600);// refresh_token的有效期 _super.configure(clients);
}
_/**_ * 配置验证管理器,UserdetailService_ */_ _@Override<br /> public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {<br /> endpoints.authenticationManager(authenticationManager)<br /> .userDetailsService(userDetailsService)<br /> .tokenStore(jwtTokenStore()) _//设置token 存储在哪里_ _.tokenEnhancer(jwtAccessTokenConverter()) ;<br /> super.configure(endpoints);<br /> }
// public TokenStore redisTokenStore(){// return new RedisTokenStore(redisConnectionFactory) ;// }__ /** jwtTokenStore_ @return */ ** public TokenStore jwtTokenStore(){ JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter()); return jwtTokenStore ; }
public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter() ; // 读取classpath 下面的密钥文件** ClassPathResource classPathResource = new ClassPathResource(“coinexchange.jks”);** // 获取KeyStoreFactory** KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,”coinexchange”.toCharArray()) ;** // 给JwtAccessTokenConverter 设置一个密钥对** _tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair(“coinexchange”,”coinexchange”.toCharArray())); return tokenConverter ; }} |
| —- |
3.4.5 修改pom.xml 文件
将之前的redis 依赖去掉
|
_
_
_
_ | | —- |
3.4.6 获取token 测试
**3.4.7 使用jwt.io 校验token**
https://jwt.io/ |
---|
一、JWT的登出问题
Jwt 使用起来不难,而且让我们将“无状态”的概念更贴切的展示出来了,但是实践就真的这么完美吗?不是,因为jwt的登出问题。
何为登出:就是用户自己点击登出后,或用户的角色/权限改变后,该token 仍然是有效的。你可以选择在前端清除该token,但是,如果用户是有技术背景的黑客呢?之前的token他保存一边,在没有过期(时间过期)时,他仍然可以使用该token。
解决方案:
4.1 在网关里面判断该token是否存在
**
| @Component
public class TokenCheckFilter implements GlobalFilter, Ordered {
@Value("${no.token.access.urls:/admin/login,/admin/validate/code}")
private Set<String> noTokenAccessUrls;<br />_// private Redis__ _@Override<br /> public int getOrder() {<br /> return 0;<br /> }
_/**_ * 实现判断用户是否携带token ,或token 错误的功能_ *_ * @param exchange_ * @param chain_ * @return_ */_ _@Override<br /> public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {<br /> _// 不需要token 就能访问_ _if (allowNoTokenAccess(exchange)) {<br /> return chain.filter(exchange);<br /> }<br /> _// 获取用户的token_ _String token = getToken(exchange);
if (StringUtils._isEmpty_(token)) { _// token 为 Empty_ _return buildUNAuthorizedResult(exchange);<br /> }<br /> if (false) {<br /> return buildUNAuthorizedResult(exchange);<br /> }
return chain.filter(exchange);<br /> }
private boolean allowNoTokenAccess(ServerWebExchange exchange) {<br /> String path = exchange.getRequest().getURI().getPath();<br /> if (noTokenAccessUrls.contains(path)) {<br /> return true;<br /> }<br /> return false;<br /> }
_/**_ * 从头里面获取_ *_ * @param exchange_ * @return_ */_ _private String getToken(ServerWebExchange exchange) {<br /> ServerHttpRequest request = exchange.getRequest();<br /> HttpHeaders headers = request.getHeaders();<br /> String authorization = headers.getFirst(HttpHeaders._AUTHORIZATION_);<br /> if (Objects._isNull_(authorization) || authorization.trim().isEmpty()) {<br /> return null;<br /> }<br /> return authorization.replace("bearer ", "");<br /> }
private Mono<Void> buildUNAuthorizedResult(ServerWebExchange exchange) {<br /> ServerHttpResponse response = exchange.getResponse();<br /> response.setStatusCode(HttpStatus._UNAUTHORIZED_);<br /> response.getHeaders().set("Content-Type", "application/json;charset=UTF-8");<br /> JSONObject jsonObject = new JSONObject();<br /> jsonObject.put("error", "unauthorized");<br /> jsonObject.put("error_description", "invalid_token");<br /> DataBuffer dataBuffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());<br /> return response.writeWith(Flux._just_(dataBuffer));<br /> }<br />} |
| —- |
4.2 模拟访问|
访问/admin/login 这样的不需要token的资源时:
访问启动的资源时:
一、授权服务器的接入
之前我们仅仅在authorizaiton-server 里面添加了一个模拟的用户:
还没有接入到我们的系统的用户数据,本节课我们来接入一下我们的用户数据
5.1 添加登录常量
在该登录常量里面,我们可以定义受支持的登录类型
| public class LoginConstant {
_/**_ * 管理员登录_ */_ _public static final String _ADMIN_TYPE _= "admin_type" ;
_/**_ * 用户/会员登录_ */_ _public static final String _MEMBER_TYPE _= "member_type" ;
} | | —- |
5.2 实现UserDetailService 接口
/** 登录的实现_ * @param username @return_ @throws UsernameNotFoundException */@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder._getRequestAttributes(); String loginType = requestAttributes.getRequest().getParameter(“logintype”); if (StringUtils._isEmpty(loginType)) { throw new AuthenticationServiceException(“请添加logintype参数”); } UserDetails userDetails = null; switch (loginType) { case LoginConstant._ADMIN_TYPE: // 管理员登录 userDetails = loadAdminUserByUsername(username); break; case LoginConstant._MEMBER_TYPE: // 会员登录 _userDetails = loadMemberUserByUsername(username); break; default: throw new AuthenticationServiceException(“暂不支持的登录方式” + loginType); } return userDetails; } |
---|
5.3 管理员用户的登录
5.3.1 RBAC模型
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
5.3.2 依赖导入
我们选择简单的jdbcTemplate 来做权限的查询操作
5.3.3 配置数据源
server: port: 9999 spring: application: name: authorization-server cloud: nacos: discovery: server-addr: nacos-server:8848 datasource: url: jdbc:mysql://mysql-server:3307/coin-exchange?useSSL=false&serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: Ltd3411?? |
---|
5.3.4 准备SQL语句
以下sql 语句都位于LoginConstant 常量里面
一、用于登录
使用用户名查询用户的SQL:
public static final String QUERY_ADMIN_SQL = “SELECT id ,username , password , status FROM sys_user WHERE username = ? “; |
---|
二、查询用户的权限
0 判断用户是否为管理员
public static final String QUERY_ROLE_CODE_SQL = “SELECT code FROM sys_role LEFT JOIN sys_user_role ON sys_role.id = sys_user_role.role_id WHERE sys_user_role.user_id= ?”; |
---|
1 用户为管理员时:(拥有全部的权限)
public static final String QUERY_ALL_PERMISSIONS = “SELECT name FROM sys_privilege”; |
---|
2 普通用户时(通过用户的角色查询用户的权限)
public static final String QUERY_PERMISSION_SQL = “SELECT * FROM sys_privilege LEFT JOIN sys_role_privilege ON sys_role_privilege.privilege_id = sys_privilege.id LEFT JOIN sys_user_role ON sys_role_privilege.role_id = sys_user_role.role_id WHERE sys_user_role.user_id = ?”; |
---|
5.3.5 代码实现
/** 登录的实现_ * @param username @return_ @throws UsernameNotFoundException */@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder._getRequestAttributes(); String loginType = requestAttributes.getRequest().getParameter(“logintype”); if (StringUtils._isEmpty(loginType)) { throw new AuthenticationServiceException(“请添加logintype参数”); } UserDetails userDetails = null; try { switch (loginType) { case LoginConstant._ADMIN_TYPE: // 管理员登录 userDetails = loadAdminUserByUsername(username); break; case LoginConstant._MEMBER_TYPE: // 会员登录 _userDetails = loadMemberUserByUsername(username); break; default: throw new AuthenticationServiceException(“暂不支持的登录方式” + loginType); } } catch (IncorrectResultSizeDataAccessException e) { throw new UsernameNotFoundException(“会员:” + username + “不存在”); } return userDetails; } |
---|
| /** 对接管理员的登录_ * @param username @return_ /private UserDetails loadAdminUserByUsername(String username) {
return jdbcTemplate.queryForObject(QUERY_ADMIN_SQL, new RowMapper
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
if (rs.wasNull()) {
throw new UsernameNotFoundException(“用户:” + username + “不存在”);
}
Long id = rs.getLong(“id”);
String password = rs.getString(“password”);
int status = rs.getInt(“status”);
User user = new User(
String.valueOf(id), // 使用用户的id 代替用户的名称,这样会使得后面的很多情况得以处理 _password,
status == 1,
true,
true,
true,
getUserPermissions(id));
return user;
}
}, username);
}
/** 通过用户的id 获取用户的权限_ * @param id @return_ /private Set
// 查询用户是否为管理员 String code = jdbcTemplate.queryForObject(_QUERY_ROLE_CODE_SQL, String.class, id);
List
if (ADMIN_CODE.equals(code)) { // 管理员 permissions = jdbcTemplate.queryForList(_QUERY_ALL_PERMISSIONS, String.class);
} else {
permissions = jdbcTemplate.queryForList(QUERY_PERMISSION_SQL, String.class, id);
}
if (permissions == null || permissions.isEmpty()) {
return Collections.EMPTY_SET;
}
return permissions
.stream()
.distinct() // 去重 .map(
perm -> new SimpleGrantedAuthority(perm) // perm - >security可以识别的权限 )
.collect(Collectors.toSet());
} |
| —- |
5.3.6 测试效果
注意,先将数据库里面sys_user表里面,admin用户的密码修改为admin
这样测试起来简单一点。
我们发现,已经获取到了Token,看看token 里面都藏了什么:
登录已经完成了
5.3.7 密码加密器
**修改:WebSecurityConfig:将之前的PasswordEncoder 修改为以下的代码:
/** 密码加密_ * @return */@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder() ; } |
---|
我们的密码加密器将会影响2 个地方:
1 第三方客户端
2 用户登录时的密码匹配
5.3.8 测试密码匹配器的效果
先获取测试数据:
public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String encode = bCryptPasswordEncoder.encode(“123456”); System.out.println(encode); System.out.println(encode1); } |
---|
输出为:
$2a$10$PmUjAcaBwua62uvzx2nbPeNw5sf0UxRz2tx/PuKloIZV1SoLQa1nG |
---|
先将数据库里面sys_user表里面,admin用户的密码修改为:
$2a$10$PmUjAcaBwua62uvzx2nbPeNw5sf0UxRz2tx/PuKloIZV1SoLQa1nG |
---|
5.4 会员登录的接入
会员没有复杂的RBAC模型处理,我们仅仅做简单的登录就可以了。
5.4.1 准备SQL 语句
public static final String QUERY_MEMBER_SQL = “SELECT id ,password , status FROM user WHERE mobile = ? or email = ? “; |
---|
5.4.2 代码实现
/** 对接会员的登录_ * @param username @return_ /private UserDetails loadMemberUserByUsername(String username) { return jdbcTemplate.queryForObject(QUERY_MEMBER_SQL, new RowMapper @Override public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException { if(rs.wasNull()){ throw new UsernameNotFoundException(“会员:” + username + “不存在”); } long id = rs.getLong(“id”); // 获取用户的id String password = rs.getString(“password”); int status = rs.getInt(“status”); return new User( String._valueOf(id), password, status == 1 , true , true , true, Arrays.asList(new SimpleGrantedAuthority(“ROLE_USER”)) ); } }, username, username); } |
---|
5.4.3 测试登录
**
一、refresh_token和过期时间
我们可以可以使用refresh_token 来为过期的token 获取一个新的token数据
6.1 添加验证方式
**6.2 获取信息的token 测试
重启后重新后期:
大家可以看见,我们获取到的数据新增了refresh_token的一项,我们来看看它里面包含那些信息:
基本和之前时没有区别的。
6.3 使用Refresh_token获取新的token
先看错误:
原因在于:我们把jwt 里面的username 换成了现在的 用户 id ,导致的。
现在,我们需要一个纠正的过程:
Refresh_token的标识:(loginConstant)
/** token的刷新_ /public static final String REFRESH_TOKEN = “REFRESH_TOKEN” ; |
---|
| @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String loginType = requestAttributes.getRequest().getParameter(“logintype”); // 区分时后台人员还是我们的用户登录 _if (StringUtils._isEmpty(loginType)) {
throw new AuthenticationServiceException(“登录类型不能为null”);
}
String grantType = requestAttributes.getRequest().getParameter(“granttype”);
UserDetails userDetails = null;
try {
if (LoginConstant._REFRESH_TOKEN.equals(grantType.toUpperCase())) {
username = adjustUsername(username, loginType); // 为refresh_token 时,需要将id->username }
switch (loginType) {
case LoginConstant._ADMIN_TYPE:
userDetails = loadSysUserByUsername(username);
break;
case LoginConstant.MEMBER_TYPE:
userDetails = loadMemberUserByUsername(username);
break;
default:
throw new AuthenticationServiceException(“暂不支持的登录方式:” + loginType);
}
} catch (IncorrectResultSizeDataAccessException e) { // 我们的用户不存在 _throw new UsernameNotFoundException(“用户名” + username + “不存在”);
}
return userDetails;<br />} |
| —- |
纠正的实现:
添加SQL语句:
| /** 使用用户的id 查询用户名称_ /public static final String QUERY_ADMIN_USER_WITH_ID = “SELECT username
FROM sys_user where id = ?” ;
/** 使用用户的id 查询用户名称_ /public static final String QUERY_MEMBER_USER_WITH_ID = “SELECT mobile
FROM user where id = ?” ; |
| —- |
实现纠正:
/** 纠正在refresh 场景下的登录问题_ * @param username @param loginType_ @return */private String adjustUsername(String username, String loginType) { if (LoginConstant._ADMIN_TYPE.equals(loginType)) { return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_USER_WITH_ID, String.class, username); } if (LoginConstant.MEMBER_TYPE.equals(loginType)) { return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_USER_WITH_ID, String.class, username); } return username; } |
---|
6.4 token 过期时间的设置
**Token的有效期为一周,
一、Token传递和获取
7.1 受保护资源之前Token的传递
Case1:
Case2:
在第一种Case 里面,我们可以从本次请求的上下文里面获取用户的token ,进行一个Token的传递。
在第二种Case 里面,我们没有一个用户请求的上下文,因此我们需要应用自己去获取一个临时的token。
这2种请求的源码实现,OAuth2.0 已经帮我们写好了,在:
获取的方式非常的简单:使用client_credentials 授权方式来进行的
7.2 在authorization-server 里面添加客户端授权的方式
/** 添加第三方的客户端_ /@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient(“coin-api”) // 第三方客户端的名称 .secret(passwordEncoder.encode(“coin-secret”)) // 第三方客户端的密钥 .scopes(“all”) //第三方客户端的授权范围 .authorizedGrantTypes(“password”,”refresh_token”) .accessTokenValiditySeconds(7 24 3600) // token的有效期 .refreshTokenValiditySeconds(30 24 3600)// refresh_token的有效期 _.and() .withClient(“inside-app”) .secret(passwordEncoder.encode(“inside-secret”)) .secret(“all”) .authorizedGrantTypes(“client_credentials”) .accessTokenValiditySeconds(7 24 3600) ; super.configure(clients); } |
---|