目录
一、依赖
二、配置
三、启动类
四、授权功能配置
五、网络安全配置
六、测试token
七、通过Redis来共享token
八、资源服务器和授权服务器交互
九、JWT来存储token
十、网关里校验tokenl来实现JWT的登出
十一、管理员登录接入
十二、会员登录的接入
十三、refresh_token和过期时间
十四、token传递和api授权
一、依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
二、配置
server:
port: 9999
spring:
application:
name: authorization-server
cloud:
nacos:
discovery:
server-addr: nacos-server:8848
username: nacos
password: nacos
三、启动类
package com.dragonwu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class AuthorizationApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorizationApplication.class,args);
}
}
四、授权功能配置
package com.dragonwu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
@EnableAuthorizationServer //开启授权服务器的功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
/**
* 添加第三方客户端
*/
@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);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
super.configure(endpoints);
}
}
五、网络安全配置
package com.dragonwu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager=new InMemoryUserDetailsManager();
User user=new User("admin","123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
inMemoryUserDetailsManager.createUser(user);
return inMemoryUserDetailsManager;
}
/**
* 密码加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}
六、测试token
打开ApiPost
POST请求,选择basic认证
添加参数
token获取成功
七、通过Redis来共享token
完成以上步骤后,token是存储在内存里的,不方便集群调用,于是我们可以采用redis来解决。
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、修改配置为
server:
port: 9999
spring:
application:
name: authorization-server
cloud:
nacos:
discovery:
server-addr: nacos-server:8848
username: nacos
password: nacos
redis:
host: redis-server
port: 6380
password: xxx
3、修改AuthorizationServerConfig.java文件为
package com.dragonwu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@EnableAuthorizationServer //开启授权服务器的功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
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);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(redisTokenStore()); //tokenStore来存储我们的token
super.configure(endpoints);
}
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
4、测试见先查看redis数据库
目前并没有数据
5、请求测试
还是之前的请求参数
token发送成功!
再查看redis,可以看到token已经被存储到了redis
八、资源服务器和授权服务器交互
1、添加控制器
package com.dragonwu.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class UserInfoController {
/**
* 当前登录用户的对象
* @param principal
* @return
*/
@GetMapping("/user/info")
public Principal userInfo(Principal principal){
//使用ThreadLocal来实现的
// Authentication authentication= SecurityContextHolder.getContext().getAuthentication();
return principal;
}
}
2、添加配置类
package com.dragonwu.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@EnableResourceServer
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}
添加了该配置以后就实现了资源服务器的功能
3、重启后,测试
(1)再次发送请求,获取到token
(2)访问如下:
获取到用户对象
这就是通过一个token换取一个用户的接口。
但这样并不好,因为并发压力还是有的,交互频繁影响性能。
解决方案如下:
九、JWT来存储token
这里注释掉之前redis的配置和依赖
jwt密钥生成见:
1、 修改配置类
package com.dragonwu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@EnableAuthorizationServer //开启授权服务器的功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
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);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// .tokenStore(redisTokenStore()); //redis tokenStore来存储我们的token
.tokenStore(jwtTokenStore()) //使用JWT来存储token
.tokenEnhancer(jwtAccessTokenConverter());
super.configure(endpoints);
}
public TokenStore jwtTokenStore(){
JwtTokenStore jwtTokenStore=new JwtTokenStore(jwtAccessTokenConverter());
return jwtTokenStore;
}
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter tokenConverter=new JwtAccessTokenConverter();
//加载我们的私钥
ClassPathResource classPathResource=new ClassPathResource("coinexchange.jks");
KeyStoreKeyFactory keyStoreKeyFactory=new KeyStoreKeyFactory(classPathResource,"coinexchange".toCharArray());
tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange","coinexchange".toCharArray()));
return tokenConverter;
}
// public TokenStore redisTokenStore(){
// return new RedisTokenStore(redisConnectionFactory);
// }
}
资源目录添加私钥
2、再次发送发现token已经改变了
3、进入jwt.io解密
解析token密文
存储实现!
十、网关里校验tokenl来实现JWT的登出
首先配置网关
1、网关里添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置:
redis:
host: redis-server
port: 6380
password: WXL1214??
2、网关里新建一个filter
package com.dragonwu.filter;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Set;
@Component
public class JwtCheckFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${no.require.urls:/admin/login}")
private Set<String> noRequireTokenUris;
/**
* 过滤拦截到用户的请求后做啥
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1:该接口是否需要token 才能访问
if(!isRequireToken(exchange)){
return chain.filter(exchange);//不需要token,直接放行
}
//2:取出用户的token
String token=getUserToken(exchange);
//3:判断用户的token 是否有效
if(StringUtils.isEmpty(token)){
return buildeNoAuthorizationResult(exchange);
}
Boolean hasKey=redisTemplate.hasKey(token);
if(hasKey!=null && hasKey){
return chain.filter(exchange);//token有效,直接放行
}
return buildeNoAuthorizationResult(exchange);
}
/**
* 给用户响应一个没有token的错误
* @param exchange
* @return
*/
private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange){
ServerHttpResponse response=exchange.getResponse();
response.getHeaders().set("Content-Type","application/json");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
JSONObject jsonObject=new JSONObject();
jsonObject.put("error","NoAuthorization");
jsonObject.put("errorMsg","Token is Null or Error");
DataBuffer wrap=response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
return response.writeWith(Flux.just(wrap));
}
/**
* 从请求头里获取用户的token
* @param exchange
* @return
*/
private String getUserToken(ServerWebExchange exchange){
String token=exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
return token==null? null:token.replace("bearer ","");
}
/**
* 判断该接口是否需要token
* @param exchange
* @return
*/
private boolean isRequireToken(ServerWebExchange exchange){
String path=exchange.getRequest().getURI().getPath();
if(noRequireTokenUris.contains(path)){
return false;//不需要token
}
return Boolean.TRUE;
}
/**
* 拦截器的顺序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
3、测试接口
可以看到不用token的接口可以直接访问到该服务,只是目前没有该服务。
访问需要token的接口时
返回了我们之前设置拦截的信息。
十一、管理员登录接入
1、新建登录常量类
package com.dragonwu.constant;
/**
* 登录的常量
*/
public class LoginConstant {
/**
* 后台管理人员
*/
public static final String ADMIN_TYPE="admin_type";
/**
* 普通的用户
*/
public static final String MEMBER_TYPE="member_type";
/**
* 使用用户名查询用户
*/
public static final String QUERY_ADMIN_SQL =
"SELECT `id` ,`username`, `password`, `status` FROM sys_user WHERE username = ? ";
/**
* 查询用户的角色Code
*/
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= ?";
/**
* 查询所有的权限名称
*/
public static final String QUERY_ALL_PERMISSIONS =
"SELECT `name` FROM sys_privilege";
/**
* 对于我们非超级用户,我们需要先查询 role->permissionId->permission
*/
public static final String QUERY_PERMISSION_SQL =
"SELECT `name` 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 = ?";
/**
* 超级管理员的角色的Code
*/
public static final String ADMIN_ROLE_CODE = "ROLE_ADMIN" ;
/**
* 会员查询SQL
*/
public static final String QUERY_MEMBER_SQL =
"SELECT `id`,`password`, `status` FROM `user` WHERE mobile = ? or email = ? ";
public static final String REFRESH_TYPE = "REFRESH_TOKEN" ;
/**
* 使用后台用户的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 = ?" ;
}
2、将之前测试的WebSecurityConfig里的userDetailsService模拟代码注释掉
添加新的userDetailsService
package com.dragonwu.service.impl;
import com.dragonwu.constant.LoginConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserServiceDetailsServiceImpl implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String loginType = requestAttributes.getRequest().getParameter("login_type"); // 区分时后台人员还是我们的用户登录
if (StringUtils.isEmpty(loginType)) {
throw new AuthenticationServiceException("登录类型不能为null");
}
UserDetails userDetails = null;
try {
String grantType = requestAttributes.getRequest().getParameter("grant_type"); // refresh_token 进行纠正
if (LoginConstant.REFRESH_TYPE.equals(grantType.toUpperCase())) {
username = adjustUsername(username, loginType);
}
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;
}
/**
* 纠正用户的名称
*
* @param username 用户的id
* @param loginType admin_type member_type
* @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;
}
/**
* 后台人员的登录
*
* @param username
* @return
*/
private UserDetails loadSysUserByUsername(String username) {
// 1 使用用户名查询用户
return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_SQL, new RowMapper<User>() {
@Override
public User 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( // 3 封装成一个UserDetails对象,返回
String.valueOf(id), //使用id->username
password,
status == 1,
true,
true,
true,
getSysUserPermissions(id)
);
}
}, username);
}
/**
* // 2 查询这个用户对应的权限
* 通过用户的id 查询用户的权限
*
* @param id
* @return
*/
private Collection<? extends GrantedAuthority> getSysUserPermissions(long id) {
// 1 当用户为超级管理员时,他拥有所有的权限数据
String roleCode = jdbcTemplate.queryForObject(LoginConstant.QUERY_ROLE_CODE_SQL, String.class, id);
List<String> permissions = null; // 权限的名称
if (LoginConstant.ADMIN_ROLE_CODE.equals(roleCode)) { // 超级用户
permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_ALL_PERMISSIONS, String.class);
} else { // 2 普通用户,需要使用角色->权限数据
permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_PERMISSION_SQL, String.class, id);
}
if (permissions == null || permissions.isEmpty()) {
return Collections.emptySet();
}
return permissions.stream()
.distinct() // 去重
.map(perm -> new SimpleGrantedAuthority(perm))
.collect(Collectors.toSet());
}
/**
* 会员的登录
*
* @param username
* @return
*/
private UserDetails loadMemberUserByUsername(String username) {
return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_SQL, new RowMapper<User>() {
@Override
public User 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);
}
}
3、配置类里添加实现类注解
4、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
5、添加数据源的配置
server:
port: 9999
spring:
application:
name: authorization-server
cloud:
nacos:
discovery:
server-addr: nacos-server:8848
username: nacos
password: nacos
datasource:
url: jdbc:mysql://mysql-server:3307/coin-exchange?useSSL=false&serverTimezon=GMT%28
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
6、再次请求测试,可以看到之前的信息被返回了
7、添加参数再次测试
可以看到token已经获取到了
8、加密器优化
修改加密方式
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
9、再次测试
可以看到新的token已经获取到了
十二、会员登录的接入
1、业务代码
package com.dragonwu.service.impl;
import com.dragonwu.constant.LoginConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserServiceDetailsServiceImpl implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String loginType = requestAttributes.getRequest().getParameter("login_type"); // 区分时后台人员还是我们的用户登录
if (StringUtils.isEmpty(loginType)) {
throw new AuthenticationServiceException("登录类型不能为null");
}
UserDetails userDetails = null;
try {
String grantType = requestAttributes.getRequest().getParameter("grant_type"); // refresh_token 进行纠正
if (LoginConstant.REFRESH_TYPE.equals(grantType.toUpperCase())) {
username = adjustUsername(username, loginType);
}
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;
}
/**
* 纠正用户的名称
*
* @param username 用户的id
* @param loginType admin_type member_type
* @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;
}
/**
* 后台人员的登录
*
* @param username
* @return
*/
private UserDetails loadSysUserByUsername(String username) {
// 1 使用用户名查询用户
return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_SQL, new RowMapper<User>() {
@Override
public User 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( // 3 封装成一个UserDetails对象,返回
String.valueOf(id), //使用id->username
password,
status == 1,
true,
true,
true,
getSysUserPermissions(id)
);
}
}, username);
}
/**
* // 2 查询这个用户对应的权限
* 通过用户的id 查询用户的权限
*
* @param id
* @return
*/
private Collection<? extends GrantedAuthority> getSysUserPermissions(long id) {
// 1 当用户为超级管理员时,他拥有所有的权限数据
String roleCode = jdbcTemplate.queryForObject(LoginConstant.QUERY_ROLE_CODE_SQL, String.class, id);
List<String> permissions = null; // 权限的名称
if (LoginConstant.ADMIN_ROLE_CODE.equals(roleCode)) { // 超级用户
permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_ALL_PERMISSIONS, String.class);
} else { // 2 普通用户,需要使用角色->权限数据
permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_PERMISSION_SQL, String.class, id);
}
if (permissions == null || permissions.isEmpty()) {
return Collections.emptySet();
}
return permissions.stream()
.distinct() // 去重
.map(perm -> new SimpleGrantedAuthority(perm))
.collect(Collectors.toSet());
}
/**
* 会员的登录
*
* @param username
* @return
*/
private UserDetails loadMemberUserByUsername(String username) {
return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_SQL, new RowMapper<User>() {
@Override
public User 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);
}
}
2、测试,参数修改
会员登录成功!
十三、refresh_token和过期时间
1、配置类里添加以后代码如下即可:
.authorizedGrantTypes("password","refresh_token")
2、测试
可以看到refresh_token已经出现
Token有效期设置为一周合理,Resfresh_token设置有效期一个月合理。
修改后的配置类
package com.dragonwu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@EnableAuthorizationServer //开启授权服务器的功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Qualifier("userServiceDetailsServiceImpl")
@Autowired
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") //第三方客户端的授权范围
.authorizedGrantTypes("password","refresh_token")
.accessTokenValiditySeconds(7*24*3600) //token的有效期
.refreshTokenValiditySeconds(30*24*3600);//refresh_token的有效期
super.configure(clients);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// .tokenStore(redisTokenStore()); //redis tokenStore来存储我们的token
.tokenStore(jwtTokenStore()) //使用JWT来存储token
.tokenEnhancer(jwtAccessTokenConverter());
super.configure(endpoints);
}
public TokenStore jwtTokenStore(){
JwtTokenStore jwtTokenStore=new JwtTokenStore(jwtAccessTokenConverter());
return jwtTokenStore;
}
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter tokenConverter=new JwtAccessTokenConverter();
//加载我们的私钥
ClassPathResource classPathResource=new ClassPathResource("coinexchange.jks");
KeyStoreKeyFactory keyStoreKeyFactory=new KeyStoreKeyFactory(classPathResource,"coinexchange".toCharArray());
tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange","coinexchange".toCharArray()));
return tokenConverter;
}
// public TokenStore redisTokenStore(){
// return new RedisTokenStore(redisConnectionFactory);
// }
}
十四、token传递和api授权
1、配置类添加如下
修改后的配置类:
package com.dragonwu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@EnableAuthorizationServer //开启授权服务器的功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Qualifier("userServiceDetailsServiceImpl")
@Autowired
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") //第三方客户端的授权范围
.authorizedGrantTypes("password","refresh_token")
.accessTokenValiditySeconds(7*24*3600) //token的有效期
.refreshTokenValiditySeconds(30*24*3600)//refresh_token的有效期
.and()
.withClient("inside-app")
.secret(passwordEncoder.encode("inside-secret"))
.authorizedGrantTypes("client_credentials")
.scopes("all")
.accessTokenValiditySeconds(7*24*3600);
super.configure(clients);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// .tokenStore(redisTokenStore()); //redis tokenStore来存储我们的token
.tokenStore(jwtTokenStore()) //使用JWT来存储token
.tokenEnhancer(jwtAccessTokenConverter());
super.configure(endpoints);
}
public TokenStore jwtTokenStore(){
JwtTokenStore jwtTokenStore=new JwtTokenStore(jwtAccessTokenConverter());
return jwtTokenStore;
}
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter tokenConverter=new JwtAccessTokenConverter();
//加载我们的私钥
ClassPathResource classPathResource=new ClassPathResource("coinexchange.jks");
KeyStoreKeyFactory keyStoreKeyFactory=new KeyStoreKeyFactory(classPathResource,"coinexchange".toCharArray());
tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange","coinexchange".toCharArray()));
return tokenConverter;
}
// public TokenStore redisTokenStore(){
// return new RedisTokenStore(redisConnectionFactory);
// }
}
2、认证测试
可以看到请求成功
认证部分完结散花!