Spring-Security

统一提供拦截器,接口实现用户权限验证,放行等安全工作。可整合OAuth2、JWT等验证模式。

此处为SpringBoot整合SpirngCouldOauth2实现JWT验证的实例


目录

  1. Maven依赖
  2. web安全配置WebSecurityConfig
  3. User对象实现UserDetails配置验证
  4. UserService实现UserDetailsService实现验证流程*
  5. AuthorizationServerConfig 授权服务器 配置
  6. JwtTokenStoreConfig JWT存储配置
  7. JwtTokenEnhancer JWT内容扩展(可选)
  8. ResourceServerConfig 资源服务器 配置
  9. 测试

1.Maven依赖

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
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>

<dependencies>
<!--oauth2依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--security依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!--jwt编码解码依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

</dependencies>

<!-- spring-cloud 依赖 -->
<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>
</dependencies>
</dependencyManagement>

2.web安全配置WebSecurityConfig

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
package io.kid1999.esystem.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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* @author kid1999
* @create 2021-01-19 16:52
* @description Spring Security配置
**/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

/**
* PasswordEncoder 密码加密规则,
* 这里使用的是不加密,推荐使用BCryptPasswordEncoder加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

/**
* 密码授权处理
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}


/**
* 拦截链,
* 其他接口必须认证后才能访问

*/
@Override
protected void configure(HttpSecurity http) throws Exception {

// 关闭跨域
http.csrf().disable();

// 授权认证 -- 可用注解代替
// 此处拦截 -> resource server 再拦截 -> 注解拦截
http.authorizeRequests()
.antMatchers("/**").permitAll();
}

}

3.User对象实现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
package io.kid1999.esystem.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Collection;

/**
* @TableName user
*/
@Data
public class User implements Serializable, UserDetails {

@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;


// 实现UserDetail的验证方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getPassword() {
return this.password;
}

@Override
public String getUsername() {
return this.username;
}

@Override
public boolean isAccountNonExpired() {
return false;
}

@Override
public boolean isAccountNonLocked() {
return false;
}

@Override
public boolean isCredentialsNonExpired() {
return false;
}

@Override
public boolean isEnabled() {
return false;
}
}

4.UserService实现UserDetailsService实现验证流程*

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
package io.kid1999.esystem.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.kid1999.esystem.dao.UserDao;
import io.kid1999.esystem.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;

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

/**
* @author kid1999
* @create 2021-01-19 17:59
* @description 比对密码是否匹配
**/
@Slf4j
@Component
public class UserService implements UserDetailsService {

@Autowired
private UserDao userDao;


@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("login " + username);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
User user = userDao.selectOne(wrapper);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 此处全部赋予user角色,可从数据库获取角色信息
authorities.add(new SimpleGrantedAuthority("ROLE_user"));
if(user == null){
throw new UsernameNotFoundException("用户不存在!");
}else{
String password = user.getPassword();
// 返回用户名,数据库中的密码,角色信息
return new org.springframework.security.core.userdetails.User(username,password,authorities);
}
}

}

5.AuthorizationServerConfig 授权服务器 配置

验证模式、数据存储、校验信息、放行规则….

此处的 client-id 和 client-secret、访问权限scope 都是自己设置的后面会用到!!!

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
101
102
103
104
package io.kid1999.esystem.config;

import io.kid1999.esystem.common.Constants;
import io.kid1999.esystem.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

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

/**
* @author kid1999
* @create 2021-01-20 14:26
* @description 授权服务器 配置
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserService userService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore jwtTokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;


/**
* Jwt 和 密码模式需要的配置
* JWT扩展(可选)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints){
// 配置jwt扩展内容
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> list = new ArrayList<>();
list.add(jwtTokenEnhancer);
list.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(list);

endpoints.authenticationManager(authenticationManager)
// 用户校验策略
.userDetailsService(userService)
// token 存储策略
.tokenStore(jwtTokenStore)
// 配置accessToken与JWT转换
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(tokenEnhancerChain);
}

/**
* 授权验证信息
* 验证:
* client-id 和 client-secret
* 访问权限scope
* token有效期
* 授权模式
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 配置client-id
.withClient(Constants.CLIENT_ID)
// 配置client 加密
.secret(passwordEncoder.encode(Constants.CLIENT_SECRET))
// 配置 Token 有效期
.accessTokenValiditySeconds(Constants.TOKEN_EXPIRE_DATE)
// 申请访问的权限范围
.scopes("all")
// 配置授权类型
.authorizedGrantTypes("refresh_token", "authorization_code","password");
}

/**
* 安全放行规则
*/

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许客户端访问 OAuth2 授权接口,否则请求 token 会返回 401
security.allowFormAuthenticationForClients();
// 第二行和第三行分别是允许已授权用户访问 checkToken 接口和获取 token 接口
security.checkTokenAccess("isAuthenticated()");
security.tokenKeyAccess("isAuthenticated()");

}
}

6.JwtTokenStoreConfig 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
package io.kid1999.esystem.config;

import io.kid1999.esystem.common.Constants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;

/**
* @author kid1999
* @create 2021-01-20 14:05
* @description JWTToken 配置
**/
@Configuration
public class JwtTokenStoreConfig {

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

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
// 配置JWT 使用的秘钥
accessTokenConverter.setSigningKey(Constants.TOKEN_SECRET);
accessTokenConverter.setVerifierKey(Constants.TOKEN_SECRET);
return accessTokenConverter;
}

/**
* jwt 内容扩展 (可选)
*/
@Bean
public JwtTokenEnhancer jwtTokenEnhancer(){
return new JwtTokenEnhancer();
}
}

7.JwtTokenEnhancer 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
package io.kid1999.esystem.config;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

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

/**
* @author kid1999
* @create 2021-01-21 18:55
* @description JWT 内容扩展器
**/
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String,Object> info = new HashMap<>();
info.put("test","infomation");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}

8.ResourceServerConfig 资源服务器 配置

此处的放行地址需自行按序配置、配置访问地址有三次机会:webconfig、此处、注解

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
package io.kid1999.esystem.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 kid1999
* @create 2021-01-20 14:38
* @description 资源服务器 配置
* 资源放行,类似责任链
**/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Autowired
private TokenStore jwtTokenStore;

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

//Http安全配置,对每个到达系统的http请求链接进行校验
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers("/user/login","/user/register","/user/*").permitAll()
.antMatchers("/oauth/**").permitAll()
// swagger start
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/configuration/ui").permitAll()
.antMatchers("/configuration/security").permitAll()
.anyRequest().authenticated();

}

}

9.测试

1.在IDEA中可以看到如下的接口,这是OAuth2模式下自带的验证接口

image-20210125173622512

2.在postman中验证接口获取access_token 即为JWT

参数为:

1
2
3
4
5
6
7
8
# 校验模式
grant_type:password
# 用户名
username:1111
# 密码
password:1111
# 适用范围,这个在校验中配置AuthorizationServerConfig
scope:all

Auth模式为 Basic Auth

此处的username 就是 JWT的Clint-id

此处的password就是 JWT的Clint-secret

image-20210125173407300

其本质就是POST此接口并携带校验信息,其中Auth就是设置了一个Header
{Authorization: Basic base64(Clint-id:Clint-secret)}

image-20210125174815650

3.在postman中携带JWT获取数据

image-20210125175210142

其本质就是POST此接口并携带校验信息,其中Auth就是设置了一个Header
{Authorization: bearer JWT}