孤尽T31训练营11权限管理笔记

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

这次课程主要是实践,这里记录一下搭建auth-center的过程

准备:生成密钥证书

第1步:生成密钥证书

下面的命令生成密钥证书,采用RSA算法,每个证书包含公钥和私钥

1
2
3
4
5
6
7
8
shell复制代码-- 创建一个文件夹,在该文件夹下执行如下命令
keytool -genkeypair -alias kaikeba -keyalg RSA -keypass kaikeba -keystore kaikeba.jks -storepass kaikeba

-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名
-storepass:密钥库的访问密码

image-20211122222036588.png

生成证书文件kaikeba.jks,搭建授权服务器时用

第2步:证书管理

1
2
3
4
5
shell复制代码-- 查询证书信息
keytool -list -keystore kaikeba.jks

-- 删除别名
keytool -delete -alias kaikeba -keystore kaikeba.jsk

image-20211122222243394.png

第3步:导出公钥

1
shell复制代码keytool -list -rfc --keystore kaikeba.jks | openssl x509 -inform pem -pubkey

将公钥保存到public.key文件,搭建资源服务器时用

搭建授权中心

第1步:创建授权中心模块

t31-auth-center

第2步:添加依赖

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
85
86
87
88
89
90
91
92
xml复制代码<?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">
<parent>
<artifactId>t31-parent</artifactId>
<groupId>com.kaikeba</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>t31-auth-center</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!--oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>com.kaikeba</groupId>
<artifactId>t31-admin-instance</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>

</project>

第3步:配置

application.yml

1
2
3
4
5
yaml复制代码spring:
application:
name: auth-center
profiles:
active: dev

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yaml复制代码spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
extension-configs[0]:
data-id: common.yaml
refresh: true
extension-configs[1]:
data-id: db.yaml
refresh: true
# 多个接口上的@FeignClient(“相同服务名”)会报错,overriding is disabled。
# 设置 为true ,即 允许 同名
main:
allow-bean-definition-overriding: true

生成的密钥证书放到resources下

第4步:启动器

AuthApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码package com.kaikeba.t31.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* 授权中心
*
* @author yangdc
* @date 2021/11/22
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class AuthApplication {

public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}

第5步:OAuth2配置

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
94
95
96
97
98
99
100
java复制代码package com.kaikeba.t31.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
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 javax.sql.DataSource;
import java.util.concurrent.TimeUnit;


/**
* OAuth2配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

@Autowired
@Qualifier("authenticationManagerBean")
AuthenticationManager authenticationManager;

@Autowired
private DataSource dataSource;

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

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
//证书路径和密钥库密码
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("kaikeba.jks"), "kaikeba".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//密钥别名
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("kaikeba"));
return converter;
}

@Bean
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置通过表oauth_client_details,读取客户端数据
clients.withClientDetails(clientDetailsService());
}

/**
* 配置token service和令牌存储方式(tokenStore
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//tokenStore
endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager);

//tokenService
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(false);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// 30天
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
endpoints.tokenServices(tokenServices);
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许表单认证
security.allowFormAuthenticationForClients()
//放行oauth/token_key(获得公钥)
.tokenKeyAccess("permitAll()")
//放行 oauth/check_token(验证令牌)
// .checkTokenAccess("isAuthenticated()");
.checkTokenAccess("permitAll()");
}
}

第6步:Spring 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
java复制代码package com.kaikeba.t31.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* Security配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

/**
* 注入自定义UserDetailService,读取rbac数据
*/
@Autowired
private UserDetailsService userDetailsService;

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

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

@Override
protected void configure(HttpSecurity http) throws Exception {
// 开放/oauth/开头的所有请求
http.requestMatchers().anyRequest()
.and().authorizeRequests().antMatchers("/oauth/**").permitAll();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 注入自定义的UserDetailsService,采用BCrypt加密
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}

第7步:UserDetailService

UserDetailService作用是从数据库中读取rbac数据,用户、密码、角色数据返回UserDetails,交给Spring 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
java复制代码package com.kaikeba.t31.auth.service.impl;

import com.kaikeba.t31.admin.po.Role;
import com.kaikeba.t31.auth.client.UserClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
* 用户详情服务实现
* 作用:读取数据库用户角色数据
*
* @author yangdc
* @date 2021/11/22
*/
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {

@Autowired
UserClient userClient;

@Autowired
PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 使用OpenFeign调用admin-service,得到用户角色数据
com.kaikeba.t31.admin.po.User user = userClient.getByUsername(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if (user != null) {
log.debug("current user = " + user);
// 获取用户的授权
List<Role> roles = userClient.selectRoleByUserId(user.getId());
// 声明授权文件
for (Role role : roles) {
if (role != null && role.getName() != null) {
// 封闭用户角色信息,必须ROLE_开头
// spring Security中权限名称必须满足ROLE_XXX
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getName());
grantedAuthorities.add(grantedAuthority);
}
}
}
log.debug("granted authorities = " + grantedAuthorities);
// 返回Spring Security框架的User对象
return new User(user.getUserName(), user.getPassword(), grantedAuthorities);
}
}

读取数据的UserFeign如下

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
java复制代码package com.kaikeba.t31.auth.client;

import com.kaikeba.t31.admin.po.Role;
import com.kaikeba.t31.admin.po.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

/**
* 用户客户端
*
* @author yangdc
* @date 2021/11/22
*/
@FeignClient(name = "admin-service", fallback = UserClient.UserClientFallback.class)
public interface UserClient extends UserApi {

@Slf4j
@Component
//这个可以避免容器中requestMapping重复
@RequestMapping("/fallback")
class UserClientFallback implements UserClient {

@Override
public User getByUsername(String username) {
log.info("异常发生,进入fallback方法");
return null;
}

@Override
public List<Role> selectRoleByUserId(Long id) {
log.info("异常发生,进入fallback方法");
return null;
}

}
}

搭建资源中心

t31-admin-service服务

第0步:配置

先前public.key复制到resources下

第1步:Jwt配置

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
java复制代码package com.kaikeba.t31.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
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.util.FileCopyUtils;

import java.io.IOException;

/**
* Jwt配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
public class JwtConfig {

public static final String public_cert = "public.key";

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Bean
@Qualifier("tokenStore")
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter);
}

@Bean
protected JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource(public_cert);

String publicKey;
try {
publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
}catch (IOException e) {
throw new RuntimeException(e);
}

// 设置校验公钥
converter.setVerifierKey(publicKey);

// 设置证书签名密码,否则报错
converter.setSigningKey("kaikeba");

return converter;
}
}

第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
java复制代码package com.kaikeba.t31.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
* 资源服务器配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Autowired
private TokenStore tokenStore;

@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/**").permitAll();
.antMatchers("/user/**").permitAll()
// 用于测试
.antMatchers("/book/**").hasRole("ADMIN")
.antMatchers("/**").authenticated();
}

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
}

第3步:security.yml

所有资源中心都要配置oauth/token_key,用来验证公钥,启动时验证

resources/security.yml

1
2
3
4
5
6
yaml复制代码security:
oauth2:
resource:
jwt:
#如果使用JWT,可以获取公钥用于 token 的验签
key-uri: http://localhost:9098/oauth/token_key

第4步:权限控制说明

Spring Security中定义了四个支持权限控制的表达式注解,分别是

  • @PreAuthorize
  • @PostAuthorize
  • @PreFilter
  • @PostFilter

其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤.在需要控制权限的方法上, 我们可以添加@PreAuthorize注解,用于方法执行前进行权限检查,校验用户当前角色是否能访问该方法.

1 开启EnableGlobalMethodSecurity

使用上述注解控制权限需要设置EnableGlobalMethodSecurity —–见第2步资源服务器配置

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
60
61
java复制代码package com.kaikeba.t31.admin.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

/**
* @author yangdc
* @date 2021/11/22
*/
@Slf4j
@RestController
public class TestController {

@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
return "product id : " + id;
}

@GetMapping("/order/{id}")
public String getOrder(@PathVariable String id) {
return "order id : " + id;
}

@GetMapping("/book/{id}")
public String getBook(@PathVariable String id) {
return "book id : " + id;
}

@GetMapping("/anno/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String getAnno(@PathVariable String id) {
return "admin id :" + id;
}

@RequestMapping("/hello")
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
public String hello() {
return "hello you ...";
}


@GetMapping("/getPrinciple")
public OAuth2Authentication getPrinciple(OAuth2Authentication oAuth2Authentication, Principal principal, Authentication authentication) {
log.info(oAuth2Authentication.getUserAuthentication().getAuthorities().toString());
log.info(oAuth2Authentication.toString());
log.info("principal.toString() " + principal.toString());
log.info("principal.getName() " + principal.getName());
log.info("authentication: " + authentication.getAuthorities().toString());

return oAuth2Authentication;
}

}

3 控制权限的方式—全局代码控制

image-20211122230658799.png

4 控制权限的方式—注解控制

image-20211122230740959.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%