SpringSecurity + oauth2 + jwt

代码从黑马程序员学成在线项目抽出来自己再修改的喔!

github:https://github.com/ishuaige/springsecuritydemo
gitee:https://gitee.com/niumazlb/springsecuritydemo

0.准备环境

① 数据库准备

数据库设计得比较简单,真正的权限控制需要 权限表、角色表、角色权限表、用户表、用户角色表

image-20230207231829955

ddl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '管理员姓名',
`password` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '管理员密码',
`role` bit(1) NULL DEFAULT NULL COMMENT '角色:1--超级管理员 0---用户',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin', '123456', b'1');
INSERT INTO `user` VALUES (2, 'admin1', '123456', b'0');
INSERT INTO `user` VALUES (3, 'admin2', '123', b'1');
INSERT INTO `user` VALUES (4, 'admin3', '123123', NULL);

② 开发环境准备

项目总体结构

image-20230207232023993

(1) 父工程

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<groupId>com.niuma</groupId>
<artifactId>springsecuritydemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2021.0.0</spring-cloud.version>
<mysql-connector-java.version>8.0.30</mysql-connector-java.version>
<mybatis-plus-boot-starter.version>3.5.2</mybatis-plus-boot-starter.version>
<fastjson.version>1.2.83</fastjson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- mySQL数据库驱动包管理 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!-- mybatis plus 集成Spring Boot启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

</project>

(2) 认证模块 auth

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.niuma</groupId>
<artifactId>springsecuritydemo</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<groupId>com.niuma</groupId>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth</name>
<description>auth</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>


<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

application.yml

spring.cloud.compatibility-verifier.enabled设置为false

禁用兼容性验证机制

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
server:
port: 8089
servlet:
context-path: /auth
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
cloud:
compatibility-verifier:
enabled: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tea_db
username: root
password: 123456
mybatis-plus:
configuration:
map-underscore-to-camel-case: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: isDelete
logic-delete-value: 1
logic-not-delete-value: 0

(3) 资源模块 demo-R

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.niuma</groupId>
<artifactId>springsecuritydemo</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>demoR</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-R</name>
<description>demo-R</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml

1
2
3
4
5
6
server:
port: 8080
spring:
cloud:
compatibility-verifier:
enabled: false

1.认证模块

当引入了 Spring Security 之后,模块就被 Spring Security 管理了,我们需要进行一些个性化的配置,让他校验我们的规则。

image-20230207235526577

〇 配置类

1.Security 安全管理配置

在项目中添加如下配置就可以实现对资源权限规则设定:

这是最终的配置

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
package com.niuma.auth.config;

/**
* 安全管理配置
* @author niuma
* @create 2023-01-30 11:06
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
@Lazy
DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;

//使用自己定义DaoAuthenticationProviderCustom来代替框架的DaoAuthenticationProvider
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProviderCustom);
}

@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

//配置用户信息服务
// @Bean
// public UserDetailsService userDetailsService() {
// //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// //在内存创建zhangsan,密码123,分配权限p1
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// //在内存创建lisi,密码456,分配权限p2
// manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
// return manager;
// }

//密码校验规则
@Bean
public PasswordEncoder passwordEncoder() {
// //密码为明文方式
return NoOpPasswordEncoder.getInstance();
// return new BCryptPasswordEncoder();
}

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
.anyRequest().permitAll()//其它请求全部放行
.and()
.formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success

http.logout().logoutUrl("/logout");//退出地址

}


}

2.授权服务器配置

注意这里配置的参数,请求要和它一致噢

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
package com.niuma.auth.config;

/**
* 授权服务器配置
*
* @author niuma
* @create 2023-01-30 11:06
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {

@Resource(name = "authorizationServerTokenServicesCustom")
private AuthorizationServerTokenServices authorizationServerTokenServices;

@Autowired
private AuthenticationManager authenticationManager;

/**
* 配置客户端细节 如 客户端 id 秘钥 重定向 url 等
*
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()// 使用in-memory存储
.withClient("niuma")// client_id
.secret("niuma")//客户端密钥
// .secret(new BCryptPasswordEncoder().encode("XcWebApp"))//客户端密钥
.resourceIds("niumaR")//资源列表
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.scopes("all")// 允许的授权范围
.autoApprove(false)//false跳转到授权页面
//客户端接收授权码的重定向地址
.redirectUris("http://localhost:8089")
;
}

//令牌端点的访问配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)//认证管理器
.tokenServices(authorizationServerTokenServices)//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}

//令牌端点的安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()") //oauth/token_key是公开
.checkTokenAccess("permitAll()") //oauth/check_token公开
.allowFormAuthenticationForClients() //表单认证(申请令牌)
;
}

}

3.Token 配置

配置 Token 的类型和生成策略

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
package com.niuma.auth.config;

/**
* TokenConfig的配置类,用来指定 oauth2 生成的 token 类型等信息
* @author niuma
* @create 2023-01-30 11:06
*/
@Configuration
public class TokenConfig {

@Autowired
@Lazy
TokenStore tokenStore;
//签名密钥
private String SIGNING_KEY = "niuma";

// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
@Lazy //不加会报循环依赖,暂时没找到别的方法
private JwtAccessTokenConverter accessTokenConverter;

@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}

//令牌管理服务
@Bean(name = "authorizationServerTokenServicesCustom")
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略

TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();//令牌增强链
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);

service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}


}

4.自定义检验规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.niuma.auth.config;

/**
* 这里继承 DaoAuthenticationProvider 自定义 身份验证,不再使用原本的校验密码,因为我们在 UserServiceImpl 自己校验了
*
* @author niuma
* @create 2023-01-30 11:06
*/
@Slf4j
@Component
public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider {

@Autowired
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}

//不再校验密码
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

}

}

① 修改用户信息校验规则

我们要从自己的数据库拿数据来校验,并且添加权限

1.自定义校验参数

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
package com.niuma.ucenter.model.dto;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
* 统一认证入口后统一提交的数据,自己扩展的属性
*
* @author niuma
* @create 2023-01-30 11:06
*/
@Data
public class AuthParamsDto {

private String username; //用户名
private String password; //域 用于扩展
private String cellphone;//手机号
private String checkCode;//验证码
private String checkCodeKey;//验证码key
private String authType; // 认证的类型 password:用户名密码模式类型 sms:短信模式类型

//附加数据,作为扩展,不同认证类型可拥有不同的附加数据。如认证类型为短信时包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情况下都包含clientId
private Map<String, Object> payload = new HashMap<>();

}

2.实现 UserDetailsService

实现 UserDetailsService 接口并实现 loadUserByUsername

loadUserByUsername 方法就是校验登录参数的

为了拓展更多的校验方法(账号密码登录,微信登录。。),我们并不写死校验逻辑,而是通过 IOC 容器拿到对应的校验方法类进行校验

loadUserByUsername 方法的参数就是请求中的 username 参数,但是我们让它拓展成我们需要的参数

最终返回一个 UserDetails 对象,里面包括了用户信息和权限

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
package com.niuma.ucenter.service.impl;

/**
* 这里实现 UserDetailsService接口 自己做用户的校验,可以从数据库中取数据
*
* @author niuma
* @create 2023-01-30 11:06
*/
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

@Resource
UserMapper userMapper;

@Resource
ApplicationContext applicationContext;

/**
* @param info 请求中带的 username 参数,因为我们可能需要更多的信息(例如登录类型,微信登陆等等),
* 所以这个参数我们可以接收一个json,再转换成dto,这样请求中的password就不用携带了,
* 将参数全部放在 username 中
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String info) throws UsernameNotFoundException {
AuthParamsDto authParamsDto = null;
try {
//将认证参数转为AuthParamsDto类型
authParamsDto = JSON.parseObject(info, AuthParamsDto.class);
} catch (Exception e) {
log.info("认证请求不符合项目要求:{}", info);
throw new RuntimeException("认证请求数据格式不对");
}
// 根据不同的登陆类型作不同的校验(通过ioc拿校验方法)
//认证方式
String authType = authParamsDto.getAuthType();
//从spring容器中拿具体的认证bean实例
AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class);
//开始认证,认证成功拿到用户信息
com.niuma.ucenter.model.domain.User user = authService.execute(authParamsDto);
// 将登陆返回的对象 封装为 UserDetails,返回 UserDetails
return getUserPrincipal(user);
}

/**
* 根据 User 对象构造一个UserDetails对象
*
* @param user
* @return
*/
public UserDetails getUserPrincipal(com.niuma.ucenter.model.domain.User user) {
//权限列表,存放的用户权限
List<String> permissionList = new ArrayList<>();

//todo 这里的权限比较简单,实际应该会有权限表,角色表,角色权限表,用户角色表等
int role = user.getRole();
if(role == 1){
permissionList.add("admin");
}else {
permissionList.add("teacher");
}
//根据用户id查询数据库中他的权限
if (permissionList.size() == 0) {
//用户权限,如果不加报 Cannot pass a null GrantedAuthority collection
permissionList.add("test");
}

String[] authorities = permissionList.toArray(new String[0]);
//原来存的是账号,现在扩展为用户的全部信息(密码不要放)
user.setPassword(null);
String jsonString = JSON.toJSONString(user);
UserDetails userDetails = User.withUsername(jsonString).password("").authorities(authorities).build();

return userDetails;
}

}

3.认证方式的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.niuma.ucenter.service;

import com.niuma.ucenter.model.domain.User;
import com.niuma.ucenter.model.dto.AuthParamsDto;

/**
* 认证接口,各种认证类型都需要实现这个接口
* @author niuma
* @create 2023-01-30 11:06
*/
public interface AuthService {

/**
* 认证方法
* @param authParamsDto 认证参数
* @return 返回认证成功的用户信息
*/
User execute(AuthParamsDto authParamsDto);

}

3.1 账号密码认证的实现类

service 注入 bean 必须按照规定命名,保证在 UserDetailsServiceImpl 中可以通过 ioc 拿到

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
package com.niuma.ucenter.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.niuma.ucenter.mapper.UserMapper;
import com.niuma.ucenter.model.domain.User;
import com.niuma.ucenter.model.dto.AuthParamsDto;
import com.niuma.ucenter.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
* 账号密码认证,service 注入bean必须按照规定命名,保证在 UserDetailsServiceImpl 中可以通过 ioc拿到
* 如果需要实现其他认证方法,可以仿照该方法
*
* @author niuma
* @create 2023-01-30 11:06
*/
@Slf4j
@Service("password_authservice")
public class PasswordAuthServiceImpl implements AuthService {

@Autowired
UserMapper userMapper;

@Autowired
PasswordEncoder passwordEncoder;

// @Autowired
// CheckCodeClient checkCodeClient; 验证码服务客户端

//实现账号和密码认证
@Override
public User execute(AuthParamsDto authParamsDto) {

// //得到验证码
// String checkcode = authParamsDto.getCheckcode();
// String checkcodekey = authParamsDto.getCheckcodekey();
// if(StringUtils.isBlank(checkcodekey) || StringUtils.isBlank(checkcode)){
// throw new RuntimeException("验证码为空");
//
// }
//
// //校验验证码,请求验证码服务进行校验
// Boolean result = checkCodeClient.verify(checkcodekey, checkcode);
// if(result==null || !result){
// throw new RuntimeException("验证码错误");
// }

//账号
String username = authParamsDto.getUsername();
//从数据库查询用户信息
User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getName, username));
if (user == null) {
//账号不存在
throw new RuntimeException("账号不存在");
}
//比对密码
String passwordDB = user.getPassword();//正确的密码(加密后)
String passwordInput = authParamsDto.getPassword();//输入的密码
boolean matches = passwordEncoder.matches(passwordInput, passwordDB);
if (!matches) {
throw new RuntimeException("账号或密码错误");
}

return user;
}
}

2.资源模块

当你微服务中的一个资源模块需要引入权限管理时,先在 pom 中添加 Spring Security 和 Oauth2 依赖。

〇 配置类

1.资源服务配置

我们需要声明我们是一个资源

注意这里的资源服务标识要跟前面的认证服务配置类里面指定的一样

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
package com.niuma.demor.config;

/**
* @author niuma
* @version 1.0
* @description 资源服务配置
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


//资源服务标识 需要与认证服务保持统一
public static final String RESOURCE_ID = "niumaR";

@Autowired
TokenStore tokenStore;

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenStore(tokenStore)
.stateless(true);
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()
;
}

}

2.Token 配置

注意这里的配置要和前面的保持统一

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
package com.niuma.demor.config;

/**
* TokenConfig的配置类,用来指定 oauth2 生成的 token 类型等信息
*
* @author niuma
* @create 2023-01-30 11:06
*/
@Configuration
public class TokenConfig {
// 签名密钥,需要与认证服务保持统一
String SIGNING_KEY = "niuma";


// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }

@Autowired
@Lazy
private JwtAccessTokenConverter accessTokenConverter;

@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}


}

① 测试接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.niuma.demor.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author niuma
* @create 2023-02-05 23:35
*/
@RestController
public class RController {

@GetMapping("/r/getR1")
public String GetR1(){
return "这是资源服务的R1";
}

@GetMapping("/r/getR2")
@PreAuthorize("hasAuthority('admin')")
public String GetR2(){
return "这是资源服务的R2,只有admin才能访问喔";
}
}

3.测试

个人想法:认证模块只是一个用来获取 Token 的地方,后续前端只需要拿着 token 访问资源就完事了

这里并没有对 jwt 做校验,一般这个工作是由网关做统一校验后分发到其他微服务的,后续有机会再做吧。

① 访问认证服务获得 token

image-20230208003115988

执行得到 token

image-20230208003158935

看看 token 存的东西,user_name 包含的就是用户信息的 json

image-20230208003335966

② 访问资源服务

注意,需要在请求头中加上 Authorization 参数喔

Authorization 参数值格式:Bearer (注意后面有个空格)

image-20230208003704549

image-20230208003902460

(1)执行获取资源请求

获取 R1 资源成功,因为没有指定权限

image-20230208004036992

获取 R2 资源不成功,因为在 R2 接口那里我们配置了只有 admin 权限可以请求

image-20230208004103898