发布于 

资源服务器授权以及OAuth2对接微服务

1. 资源服务器授权配置

1.1 资源服务授权配置

基本上所有微服务都是资源服务。

1. 资源服务中配置公钥

  • 认证服务生成令牌采用非对称加密算法,认证服务采用私钥加密生成令牌,对外向资源服务提供公钥,资源服务使用公钥来校验令牌的合法性。 将公钥拷贝到 public.key 文件中,将此文件拷贝到每一个需要的资源服务工程的classpath(resources目录)下,eg: 用户微服务:

2. 资源服务中添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

3. 配置每个系统的Http请求路径安全控制策略以及读取公钥信息识别令牌,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* @Auther: csp1999
* @Date: 2021/01/26/19:41
* @Description: 资源授权配置(访问该微服务时,需要校验令牌)
*/
@Configuration
@EnableResourceServer // 开启资源校验服务 ---> 令牌校验
// 开启全局方法安全校验(方法权限控制:不同权限的登录用户,可以访问的方法不同)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)// 激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* 公钥
*/
private static final String PUBLIC_KEY = "public.key";
/**
* 定义JwtTokenStore
*
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/**
* 定义JJwtAccessTokenConverter
*
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密的公钥Key
*
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/**
* Http安全配置,对每个到达系统的http请求链接进行校验
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
// 所有请求必须认证通过
http.authorizeRequests()
// 下边的路径放行
// 放行的请求路径不用登陆也可以访问
.antMatchers(
"/user/add","/user/load/*"). // 配置地址放行
permitAll()
.anyRequest().
authenticated(); // 其他地址需要认证授权
}
}

1.2 用户微服务资源授权

1.将生成的公钥public.key拷贝到changgou-service-user微服务工程的resources目录下,如下图:

2.引入依赖:

  • 在changgou-service-user微服务工程pom.xml中引入上面的oauth2.0依赖

3.资源授权配置:

  • 在changgou-service-user工程中创建com.changgou.user.config.ResourceServerConfig(上边有)

1.3 授权测试

用户每次访问微服务的时候,需要先申请令牌,令牌申请后,每次将令牌放到头文件中,才能访问微服务。

头文件中每次需要添加一个Authorization头信息,头信息的内容格式为:bearer token(token为申请得到的令牌)。

1.不携带令牌测试:

访问http://localhost:8089/user 不携带令牌,结果如下:

2.携带正确令牌访问

先通过认证微服务执行登录,拿到令牌:

访问localhost:9001/user/login?username=szitheima&password=szitheima获取token令牌,结果如下:

访问http://localhost:8089/user/szitheima(携带正确令牌),根据username查询用户信息

执行后结果如下:

3.携带错误令牌

访问http://localhost:18089/user/szitheima携带不正确的token令牌,结果如下:

2. OAuth2对接微服务

用户每次访问微服务的时候,先去oauth2.0服务登录,登录后再访问微服务网关,微服务网关将请求转发给其他微服务处理。

步骤:

  • 1.用户登录成功后,会将令牌信息存入到cookie中(一般建议存入到头文件中)
  • 2.用户携带Cookie中的令牌访问微服务网关
  • 3.微服务网关先获取头文件中的令牌信息,如果Header中没有Authorization令牌信息,则去参数中找,参数中如果没有,则去Cookie中找Authorization,最后将令牌信息封装到Header中,然后再调用其他微服务
  • 4.其他微服务会获取头文件中的Authorization令牌信息,然后匹配令牌数据是否能使用公钥解密,如果解密成功说明用户已登录,解密失败,说明用户未登录

2.1 令牌加入到Header中

修改changgou-gateway-web的全局过滤器com.changgou.filter.AuthorizeFilter,实现将令牌信息添加到头文件中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
* @Auther: csp1999
* @Date: 2021/01/24/20:17
* @Description: 全局过滤器: 用于鉴权(获取令牌 + 解析令牌 + 判断令牌是否合法)
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
// 令牌头
private static final String AUTHORIZE_TOKEN = "Authorization";
/**
* 全局过滤器
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求对象
ServerHttpRequest request = exchange.getRequest();
// 2.获取响应对象
ServerHttpResponse response = exchange.getResponse();
// 获取请求的URI
String path = request.getURI().getPath();
// 3.未登录状态下只放行登录login和搜索search
if (path.startsWith("/api/user/login") || path.startsWith("/api/brand/search/")) {
// 直接放行
return chain.filter(exchange);
}
// 4.判断是否为登录的URL 如果不是则权限校验
// 4.1 从头header中获取令牌数据
String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
// 如果为true:说明令牌在header中, false:令牌不在header中,将令牌封装入header,再传递给其他微服务
boolean hasToken = true;
// 如果header中没有token数据
if(StringUtils.isEmpty(token)){
// 4.2 从cookie中中获取令牌数据
HttpCookie first = request.getCookies().getFirst(AUTHORIZE_TOKEN);
if(first!=null){
token=first.getValue();// 就是令牌的数据
}
// 令牌不在header中
hasToken = false;
}
// 如果cookie中也没有令牌数据,则从请求参数中获取
if(StringUtils.isEmpty(token)){
// 4.3 从请求参数中获取令牌数据
token= request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
// 令牌不在header中
hasToken = false;
}
// 如果请求参数中仍然没有token数据
if(StringUtils.isEmpty(token)){
// 4.4. 则,结束,响应UNAUTHORIZED状态码405.
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 如果得到token数据,则:
// 5.解析令牌数据(判断解析是否正确,正确就放行,否则就结束)
try {
// 1.借助jwt工具类解析token校验令牌
// Claims claims = JwtUtil.parseJWT(token);
// 2.借助oautho2校验令牌
// 如果请求头中没有token:
if (!hasToken) {
// token令牌不为空,则判断令牌是否有bearer前缀,如果没有则添加该前缀
if (!token.startsWith("bearer") && !token.startsWith("Bearer")) {
token = "bearer" + token;
}
// 如果有前缀,放行
// 放行之前,将令牌封装到头文件中(这一步是为了方便AUTH2校验令牌)
request.mutate().header(AUTHORIZE_TOKEN, token);
}
} catch (Exception e) {
e.printStackTrace();
// 解析失败,响应状态码401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 放行
return chain.filter(exchange);
}
/**
* 过滤器执行顺序
*
* @return
*/
@Override
public int getOrder() {
// 首位
return 0;
}
}

访问测试:

访问http://localhost:8001/api/user/szitheima,将生成的新令牌放到头文件中,在令牌前面添加Bearer,这里主要由个空格,效果如下:

2.2 用户身份权限控制

由于我们项目使用了微服务,任何用户都有可能使用任意微服务,此时我们需要控制相关权限,例如:普通用户角色不能使用用户的删除操作,只有管理员才可以使用,那么这个时候就需要使用到SpringSecurity的权限控制功能了。

2.2.1 角色身份定义

在changgou-user-oauth2认证微服务中,com.changgou.oauth.config.UserDetailsServiceImpl该类实现了加载用户相关信息,如下代码:

上述代码给登录用户定义了2个角色,分别为useradmin,这一块我们目前使用的是硬编码方式将角色写死了,后面会从数据库加载。

2.2.2 角色权限控制

在每个微服务中,需要获取用户的角色,然后根据角色识别是否允许操作指定的方法,Spring Security中定义了四个支持权限控制的表达式注解,分别是@PreAuthorize@PostAuthorize@PreFilter@PostFilter。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。在需要控制权限的方法上,我们可以添加@PreAuthorize注解,用于方法执行前进行权限检查,校验用户当前角色是否能访问该方法。

1.开启@PreAuthorize

在changgou-user-service的ResourceServerConfig类上添加@EnableGlobalMethodSecurity注解,用于开启@PreAuthorize的支持,代码如下:

2.方法权限控制

在changgoug-service-user微服务的com.changgou.user.controller.UserController类的findAll()方法(查询所有用户信息)上添加权限控制注解@PreAuthorize,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 查询User全部数据
*
* @return
*/
//@PreAuthorize("hasAnyRole('user','admin')")
// 添加权限:只允许管理员admin角色访问,不允许其他角色访问
@PreAuthorize("hasAnyRole('admin')")
@GetMapping
public Result<List<User>> findAll() {
// 调用UserService实现查询所有User
List<User> list = userService.findAll();
return new Result<List<User>>(true, StatusCode.OK, "查询成功", list);
}

3.测试用户角色权限

访问localhost:8001/api/user获取所有用户信息:

发现上面请求无法访问,因为用户登录的时候,角色不包含admin角色,而findAll()方法需要admin角色,所以被拦截了。

接下来再测试其他没有加admin管理员权限的方法,其他方法(eg: findById())没有配置拦截,所以用户登录后就会放行:

知识点说明:

如果希望一个方法能被多个角色访问,配置:@PreAuthorize("hasAnyAuthority('admin','user')")

如果希望一个都能被多个角色访问,在类上配置:@PreAuthorize("hasAnyAuthority('admin','user')")

3. OAuth2动态加载数据(从数据库获取数据)

前面OAuth2我们用的数据都是静态的,在现实工作中,数据都是从数据库加载的,所以我们需要调整一下OAuth服务,从数据库加载相关数据。

  • 客户端数据[生成令牌相关数据]
  • 用户登录账号密码从数据库加载

3.1 客户端信息相关数据加载

3.1.1 数据介绍

客户端静态数据

changgou-user-oauth的com.changgou.oauth.config.AuthorizationServerConfig类中配置了客户端静态数据,主要用于配置客户端数据,代码如下:

客户端表结构

创建一个数据库changgou_oauth,并在数据库中创建一张表,表主要用于记录客户端相关信息,表结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL COMMENT '客户端ID,主要用于标识对应的应用',
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL COMMENT '客户端秘钥,BCryptPasswordEncoder加密算法加密',
`scope` varchar(256) DEFAULT NULL COMMENT '对应的范围',
`authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '认证模式',
`web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '认证后重定向地址',
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL COMMENT '令牌有效期',
`refresh_token_validity` int(11) DEFAULT NULL COMMENT '令牌刷新周期',
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

字段说明:

1
2
3
4
5
6
7
client_id:客户端id 
resource_ids:资源id(暂时不用)
client_secret:客户端秘钥
scope:范围
access_token_validity:访问token的有效期(秒)
refresh_token_validity:刷新token的有效期(秒)
authorized_grant_type:授权类型:authorization_code,password,refresh_token,client_credentials

导入2条记录到表中,SQL如下:数据中密文分别为changgou、szitheima

1
2
INSERT INTO `oauth_client_details` VALUES ('changgou', null, '$2a$10$wZRCFgWnwABfE60igAkBPeuGFuzk74V2jw3/trkdUZpnteCtJ9p9m', 'app', 'authorization_code,password,refresh_token,client_credentials', 'http://localhost', null, '432000000', '432000000', null, null);
INSERT INTO `oauth_client_details` VALUES ('szitheima', null, '$2a$10$igxoCZxTbjWx5TrmfWEEpe/WFdwbUhbxik9BKTe9i64ZOSfnu/lqe', 'app', 'authorization_code,password,refresh_token,client_credentials', 'http://localhost', null, '432000000', '432000000', null, null);

上述表结构属于SpringSecurity Oauth2.0所需的一个认证表结构,不能随意更改。相关操作在其他类中有所体现,如:org.springframework.security.oauth2.provider.client.JdbcClientDetailsService中的片段代码如下:

3.1.2 从库数据加载相关数据

修改连接配置

从数据库加载数据,我们需要先配置数据库连接,在changgou-user-oauth2的application.yml中配置连接信息,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
server:
# 认证微服务端口
port: 9001
# spring 相关配置
spring:
application:
# 微服务名称
name: user-auth
# Redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 8.131.66.136
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password: csp19990129
# 数据库相关配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/changgou_oauth?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
username: root
password: root
main:
allow-bean-definition-overriding: true
# eureka相关配置
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
# auth相关配置
auth:
# token存储到redis的过期时间
ttl: 3600
# client唯一id
clientId: changgou
# client密钥
clientSecret: changgou
# cookie域名
cookieDomain: localhost
cookieMaxAge: -1
# 本地证书、密钥以及证书密码配置
encrypt:
key-store:
# 证书路径(resources路径下)
location: classpath:/changgou.jks
# 密钥
# 公钥(提供给每个微服务),可以加密,用于校验令牌的合法性--->私钥(提供给认证微服务),可以解密,用于生成令牌--->非对称加密算法RSA
# 我们之前做的MD5加密算法,使用的是摘要加密算法,不可逆!
# AES/DESC 使用的是对称加密,可以加密和解密,加密解密的密钥是相同的!
secret: changgou
# 证书别名
alias: changgou
# 证书密码
password: changgou

修改客户端加载源

修改changgou-user-oauth2的com.changgou.oauth.config.AuthorizationServerConfig类的configure方法,将之前静态的客户端数据变成从数据库加载,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 客户端信息配置
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 静态客户端信息配置
/*
clients.inMemory()
.withClient("changgou") // 客户端id
.secret("changgou") // 秘钥
.redirectUris("http://localhost") // 重定向地址
.accessTokenValiditySeconds(3600) // 访问令牌有效期
.refreshTokenValiditySeconds(3600) // 刷新令牌有效期
.authorizedGrantTypes(
"authorization_code", // 根据授权码生成令牌
"client_credentials", // 客户端认证
"refresh_token", // 刷新令牌
"password") // 密码方式认证
.scopes("app"); // 客户端范围,名称自定义,必填
*/
// 从数据库中获取上面的数据:
clients.jdbc(dataSource).clients(clientDetails());// 从数据库加载客户端信息
}

UserDetailsServiceImpl修改

将之前的加密方式去掉即可,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//====================================客户端信息认证 开始====================================
// 取出身份,如果身份为空说明没有认证
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret
if (authentication == null) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
if (clientDetails != null) {
// 秘钥
String clientSecret = clientDetails.getClientSecret();
// 1.静态方式
//return new User(
// username, // 客户端id
// new BCryptPasswordEncoder().encode(clientSecret), // 客户端密钥->加密操作
// AuthorityUtils.commaSeparatedStringToAuthorityList(""));// 权限
// 2.数据库查找方式
return new User(
username,// 客户端id
clientSecret,// 客户端密钥->不需要再加密操作,因为数据库中已经加密
AuthorityUtils.commaSeparatedStringToAuthorityList(""));// 权限
}
}
//====================================客户端信息认证 结束====================================
...
}

3.1.3 测试

授权码模式测试

访问:http://localhost:9001/oauth/authorize?client_id=szitheima&response_type=code&scop=app&redirect_uri=http://localhost效果如下:

用户名对应应用id,密码对应秘钥。账号输入:szitheima 密码:szitheima,效果如下:

密码模式授权测试

我们之前编写的账号密码登录代码如下,每次都会加载指定的客户端ID和指定的秘钥,所以此时的客户端ID和秘钥固定了,输入的账号密码不再是客户端ID和秘钥了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 登录方法:
* 1.密码模式认证-授权方式:grant_type=password
*
* @param username 2.账号 szitheima
* @param password 3.密码 szitheima
* @return
*/
@RequestMapping("/login")
public Result<Map> login(String username, String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new RuntimeException("用户名/密码不允许为空");
}
// 申请令牌:调用loginService的login方法进行登录,并返回生成的令牌数据
AuthToken authToken = loginService.login(username, password, clientId, clientSecret, GRAND_TYPE);
// 设置到cookie中
this.saveCookie(authToken.getAccessToken());
return new Result<>(true, StatusCode.OK, "令牌生成成功", authToken);
}
// 保存token到cookie中
private void saveCookie(String token) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
CookieUtil.addCookie(response, cookieDomain, "/", "Authorization", token, cookieMaxAge, false);
}

OAuth中的com.changgou.oauth.config.UserDetailsServiceImpl配置如下:

用户每次输入账号和密码,只要密码是szitheima,即可登录成功。

访问地址http://localhost:9001/user/login 输入账号密码均为szitheima,效果如下:

3.2 用户相关数据加载

因为我们目前整套系统是对内提供登录访问,所以每次用户登录的时候oauth需要调用用户微服务查询用户信息,如上图:

我们需要在用户微服务中提供用户信息查询的方法,并在oauth中使用feign调用即可。

在真实工作中,用户和管理员对应的oauth认证服务器会分开,网关也会分开,我们今天的课堂案例只实现用户相关的认证即可。

1.Feign创建

在changgou-service-user-api中创建com.changgou.user.feign.UserFeign,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @Auther: csp1999
* @Date: 2021/01/27/14:19
* @Description: 用户微服务feign客户端接口
*/
@FeignClient(value = "changgou-user")
@RequestMapping(value = "/user")
public interface UserFeign {
/***
* 根据ID查询User数据
* @param id
* @return
*/
@GetMapping({
"/load/{id}"})
//@GetMapping({"/{id}", "/load/{id}"})不能这样写!
public Result<User> findById(@PathVariable String id);
}

2.修改UserController

修改changgou-service-user的UserController的findById方法,添加一个新的地址,用于加载用户信息,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
/***
* 根据ID查询User数据
* @param id
* @return
*/
@GetMapping({
"/{id}","/load/{id}"})
public Result<User> findById(@PathVariable String id) {
// 调用UserService实现根据主键查询User
User user = userService.findById(id);
return new Result<User>(true, StatusCode.OK, "查询成功", user);
}

3.放行查询用户方法

因为oauth需要调用查询用户信息,需要在changgou-service-user中放行/user/load/{id}方法,修改ResourceServerConfig,添加对/user/load/{id}的放行操作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Http安全配置,对每个到达系统的http请求链接进行校验
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
// 所有请求必须认证通过
http.authorizeRequests()
// 下边的路径放行
// 放行的请求路径不用登陆也可以访问
.antMatchers(
"/user/add","/user/load/*"). // 配置地址放行
permitAll()
.anyRequest().
authenticated(); // 其他地址需要认证授权
}

4.oauth调用查询用户信息

oauth认证微服务引入对changgou-user-api的依赖

1
2
3
4
5
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou-service-user-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

修改oauth的com.changgou.oauth.config.UserDetailsServiceImplloadUserByUsername方法,调用UserFeign查询用户信息,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//====================================用户信息认证 开始====================================
if (StringUtils.isEmpty(username)) {
return null;
}
// 从数据库加载查询用户信息
Result<com.changgou.user.pojo.User> userResult = userFeign.findById(username);
// 判空
if (userResult == null || userResult.getData() == null) {
return null;
}
// 根据用户名查询用户信息
//String pwd = new BCryptPasswordEncoder().encode("szitheima");
String pwd = userResult.getData().getPassword();
// 创建User对象
String permissions = "user,admin";// 指定用户的角色身份,普通用户user/admin用户
UserJwt userDetails = new UserJwt(username, pwd, AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
//====================================用户信息认证 结束====================================

完整的UserDetailsServiceImpl代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 自定义授权认证类
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
private UserFeign userFeign;
/**
* 自定义授权认证
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//====================================客户端信息认证 开始====================================
// 取出身份,如果身份为空说明没有认证
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret
if (authentication == null) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
if (clientDetails != null) {
// 秘钥
String clientSecret = clientDetails.getClientSecret();
// 1.静态方式
//return new User(
// username, // 客户端id
// new BCryptPasswordEncoder().encode(clientSecret), // 客户端密钥->加密操作
// AuthorityUtils.commaSeparatedStringToAuthorityList(""));// 权限
// 2.数据库查找方式
return new User(
username,// 客户端id
clientSecret,// 客户端密钥->不需要再加密操作,因为数据库中已经加密
AuthorityUtils.commaSeparatedStringToAuthorityList(""));// 权限
}
}
//====================================客户端信息认证 结束====================================
//====================================用户信息认证 开始====================================
if (StringUtils.isEmpty(username)) {
return null;
}
// 从数据库加载查询用户信息
Result<com.changgou.user.pojo.User> userResult = userFeign.findById(username);
// 判空
if (userResult == null || userResult.getData() == null) {
return null;
}
// 根据用户名查询用户信息
//String pwd = new BCryptPasswordEncoder().encode("szitheima");
String pwd = userResult.getData().getPassword();
// 创建User对象
String permissions = "user,admin";// 指定用户的角色身份,普通用户user/admin用户
UserJwt userDetails = new UserJwt(username, pwd, AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
//====================================用户信息认证 结束====================================
//userDetails.setComy(songsi);
return userDetails;
}
}

5.主启动类Feign开启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @Author: csp1999
* @Date: 2020/7/6 8:01
* @Description: OAUTH2 认证授权微服务启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
//@MapperScan(basePackages = "com.changgou.auth.dao")
@EnableFeignClients("com.changgou.user.feign")
public class OAuthApplication {
public static void main(String[] args) {
SpringApplication.run(OAuthApplication.class, args);
}
@Bean(name = "restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

6.测试

我们换个数据库中的账号密码登录,分别输入zhangsan,效果如下: