背景

前段时间做了一个项目, 因为涉及到权限认证, 所以分别调研了 SpringSecurity 和 Apache Shiro. 最后选择使用了 SpringSecurity + JWT做权限认证, 现在项目已经结束, 总相关笔记.
项目下载地址 jwt-demo

  1. 使用JWT生成token
  2. token存储在数据库中
  3. 使用 application/json 登录
  4. 使用手机号进行登录
  5. URI动态拦截

配置过程

添加依赖

  1. 分别添加 SpringSecurity JWT 和 fastjson 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>

基础准备对象

  • 主要是在用户登录成功handle时使用JWT生成Token返回给客户端.

基础使用dto

请求返回基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
public class BaseReqDto implements Serializable {

private String version;

}

@Data
public class BaseRespDto implements Serializable {

private String resultCode;

private String resultMsg;

private String resultTime;

}

登录请求返回对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class LoginReqDto {

private String username;

private String token;

}

@Data
public class LoginRespDto extends BaseRespDto {

private String 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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.liuzhihang.demo.bean;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

/**
* 用户信息校验验证码
*
* @author liuzhihang
*/
public class UserDetailsImpl implements UserDetails, Serializable {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 权限集合
*/
private Collection<? extends GrantedAuthority> authorities;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}

public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}

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

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

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

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

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

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

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

用户未登录handle

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

/**
* 用户登录认证, 未登录返回信息
*
* @author liuzhihang
* @date 2019-06-04 13:52
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {

response.setContentType("application/json;charset=UTF-8");

LoginRespDto respDto = new LoginRespDto();
respDto.setResultCode("0001");
respDto.setResultMsg("用户未登录");
respDto.setResultTime(LocalDateTime.now().format(FORMATTER));

response.getWriter().write(JSON.toJSONString(respDto));
}
}

用户登录验证失败handle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 用户登录认证失败返回的信息
*
* @author liuzhihang
* @date 2019-06-04 13:57
*/
@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {

response.setContentType("application/json;charset=UTF-8");

LoginRespDto respDto = new LoginRespDto();
respDto.setResultCode("0001");
respDto.setResultMsg("用户登录认证失败");
respDto.setResultTime(LocalDateTime.now().format(FORMATTER));

response.getWriter().write(JSON.toJSONString(respDto));
}
}

用户无权访问handle

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
/**
* 当用户访问无权限页面时, 返回信息
*
* @author liuzhihang
* @date 2019-06-04 14:03
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {

response.setContentType("application/json;charset=UTF-8");

LoginRespDto respDto = new LoginRespDto();
respDto.setResultCode("0002");
respDto.setResultMsg("用户无权访问");
respDto.setResultTime(LocalDateTime.now().format(FORMATTER));

response.getWriter().write(JSON.toJSONString(respDto));

}
}

用户登录成功handle

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

/**
* 用户登录成功之后的返回信息
*
* @author liuzhihang
* @date 2019-06-04 14:20
*/
@Slf4j
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

@Resource
private JwtTokenUtil jwtTokenUtil;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {

UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();

String jwtToken = jwtTokenUtil.generateToken(userDetails);

// 把生成的token更新到数据库中
// 更新DB操作 ...

response.setContentType("application/json;charset=UTF-8");

LoginRespDto respDto = new LoginRespDto();
respDto.setToken(jwtToken);
respDto.setResultCode("0000");
respDto.setResultMsg("登录成功");
respDto.setResultTime(LocalDateTime.now().format(FORMATTER));

response.getWriter().write(JSON.toJSONString(respDto));

}
}

JwtTokenUtil

主要用来生成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
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package com.liuzhihang.demo.utils;

import com.liuzhihang.demo.bean.UserDetailsImpl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.Date;

/**
* 使用 java-jwt jwt类库
*
* @author liuzhihang
* @date 2019-06-05 09:22
*/
@Component
public class JwtTokenUtil {

private static final SignatureAlgorithm SIGN_TYPE = SignatureAlgorithm.HS256;

public static final String SECRET = "jwt-secret";

/**
* JWT超时时间
*/
public static final long EXPIRED_TIME = 7 * 24 * 60 * 60 * 1000L;

/**
* claims 为自定义的私有声明, 要放在前面
* <p>
* 生成token
*/
public String generateToken(UserDetails userDetails) {

long instantNow = Instant.now().toEpochMilli();

Claims claims = Jwts.claims();
claims.put(Claims.SUBJECT, userDetails.getUsername());

return Jwts.builder().setClaims(claims).setIssuedAt(new Date(instantNow))
.setExpiration(new Date(instantNow + EXPIRED_TIME))
.signWith(SIGN_TYPE, SECRET).compact();
}

/**
* claims 为自定义的私有声明, 要放在前面
* <p>
* 生成token
*/
public String generateToken(String userName) {

long instantNow = Instant.now().toEpochMilli();

Claims claims = Jwts.claims();
claims.put(Claims.SUBJECT, userName);

return Jwts.builder().setClaims(claims).setIssuedAt(new Date(instantNow))
.setExpiration(new Date(instantNow + EXPIRED_TIME))
.signWith(SIGN_TYPE, SECRET).compact();
}

/**
* 将token解析, 映射为 UserDetails
*
* @param jwtToken
* @return
*/
public UserDetails getUserDetailsFromToken(String jwtToken) {

Claims claimsFromToken = getClaimsFromToken(jwtToken);

String userName = claimsFromToken.get(Claims.SUBJECT, String.class);

UserDetailsImpl userDetails = new UserDetailsImpl();
userDetails.setUsername(userName);

return userDetails;
}

/**
* 验证token
*/
public Boolean validateToken(String token, UserDetails userDetails) {
UserDetailsImpl user = (UserDetailsImpl) userDetails;
String username = getPhoneNoFromToken(token);

return (username.equals(user.getUsername()) && !isTokenExpired(token));
}

/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);

long instantNow = Instant.now().toEpochMilli();

refreshedToken = Jwts.builder().setClaims(claims).setIssuedAt(new Date(instantNow))
.setExpiration(new Date(instantNow + EXPIRED_TIME))
.signWith(SIGN_TYPE, SECRET).compact();
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}

/**
* 获取token是否过期
*/
public Boolean isTokenExpired(String token) {
Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}

/**
* 根据token获取username
*/
public String getPhoneNoFromToken(String token) {
return getClaimsFromToken(token).getSubject();
}

/**
* 获取token的过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimsFromToken(token).getExpiration();
}

/**
* 解析JWT
*/
private Claims getClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
}

}

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
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
105
106
107
108
109
110
111
112
113
114
115
116
package com.liuzhihang.demo.config;

import com.liuzhihang.demo.filter.CustomizeAuthenticationFilter;
import com.liuzhihang.demo.filter.JwtPerTokenFilter;
import com.liuzhihang.demo.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
* @author liuzhihang
* @date 2019-06-03 14:25
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailServiceImpl userDetailServiceImpl;

@Resource
private JwtPerTokenFilter jwtPerTokenFilter;

@Resource(name = "authenticationEntryPointImpl")
private AuthenticationEntryPoint authenticationEntryPoint;

@Resource(name = "authenticationSuccessHandlerImpl")
private AuthenticationSuccessHandler authenticationSuccessHandler;

@Resource(name = "authenticationFailureHandlerImpl")
private AuthenticationFailureHandler authenticationFailureHandler;

@Resource(name = "accessDeniedHandlerImpl")
private AccessDeniedHandler accessDeniedHandler;

/**
* 创建用于认证授权的用户
*
* @param auth
* @throws Exception
*/
@Autowired
public void configureUserInfo(AuthenticationManagerBuilder auth) throws Exception {

// 放入自己的认证授权用户, 内部逻辑需要自己实现
// UserDetailServiceImpl implements UserDetailsService
auth.userDetailsService(userDetailServiceImpl);

}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 使用JWT, 关闭session
.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint)

// 登录的权限, 成功返回信息, 失败返回信息
.and().formLogin().permitAll()

.loginProcessingUrl("/login")

// 配置url 权限 antMatchers: 匹配url 权限
.and().authorizeRequests()
.antMatchers("/login", "/getVersion")
.permitAll()
// 其他需要登录才能访问
.anyRequest().access("@dynamicAuthorityService.hasPermission(request,authentication)")

// 访问无权限 location 时
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)

// 自定义过滤
.and().addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtPerTokenFilter, UsernamePasswordAuthenticationFilter.class)

.headers().cacheControl();

}


/**
* 密码加密器
*/
@Bean
public PasswordEncoder passwordEncoder() {
/**
* BCryptPasswordEncoder:相同的密码明文每次生成的密文都不同,安全性更高
*/
return new BCryptPasswordEncoder();
}

@Bean
CustomizeAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
filter.setAuthenticationFailureHandler(authenticationFailureHandler);
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}

}

登录校验过程

graph TD; A(请求登录) --> B(CustomizeAuthenticationFilter#attemptAuthentication 解析请求的json); B --> C(UserDetailServiceImpl#loadUserByUsername 验证用户名密码); C --> D(AuthenticationSuccessHandlerImpl#onAuthenticationSuccess 构建返回参数 包括token); D --> E(返回结果)

自定义拦截器解析 json 报文

前端请求登录报文类型为 application/json 需要后端增加拦截器, 对登录请求报文进行解析

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
package com.liuzhihang.demo.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

/**
*
* 自定义拦截器, 重写UsernamePasswordAuthenticationFilter 从而可以处理 application/json 中的json请求报文
*
* @author liuzhihang
* @date 2019-06-12 19:04
*/
@Slf4j
public class CustomizeAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {

// attempt Authentication when Content-Type is json
if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
try {
BufferedReader br = request.getReader();
String str;
StringBuilder jsonStr = new StringBuilder();
while ((str = br.readLine()) != null) {
jsonStr.append(str);
}

log.info("本次登录请求参数:{}", jsonStr);

JSONObject jsonObject = JSON.parseObject(jsonStr.toString());

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
jsonObject.getString("username"), jsonObject.getString("password"));
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (IOException e) {
log.info("用户登录, 请求参数 不正确");
throw new AuthenticationServiceException("获取报文请求参数失败");
} catch (JSONException e) {
log.info("用户登录, 请求报文格式 不正确");
throw new AuthenticationServiceException("请求报文, 转换Json失败");
}
} else {
log.error("用户登录, contentType 不正确");
throw new AuthenticationServiceException(
"请求 contentType 不正确, 请使用 application/json;charset=UTF-8 或者 application/json;");
}

}

}

用户认证模块

  • 根据获取到的username从数据库中查询到密码, 将用户名密码赋值给UserDetails对象, 返回其他的框架会进行校验
  • 这边使用中是使用的手机号+验证码登录, 所以 上面json解析的也是 phoneNo+verificationCode
  • 在这块 username仅仅代指登录名, 可以是手机号可以是别的.
  • 这边使用中验证码是从redis中获取的. 获取不到返回失败, 获取到和传递的不一致也算失败.
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 com.liuzhihang.demo.service;

import com.liuzhihang.demo.bean.UserDetailsImpl;
import lombok.extern.slf4j.Slf4j;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
* @author liuzhihang
*/
@Slf4j
@Component("userDetailServiceImpl")
public class UserDetailServiceImpl implements UserDetailsService {


/**
* 用来验证登录名是否有权限进行登录
*
* 可以通过数据库进行校验 也可以通过redis 等等
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {


UserDetailsImpl userDetailsImpl = new UserDetailsImpl();
userDetailsImpl.setUsername("liuzhihang");
userDetailsImpl.setPassword(new BCryptPasswordEncoder().encode("123456789"));
return userDetailsImpl;
}

}

请求校验过程

graph TD; A(请求接口) --> B(JwtPerTokenFilter#doFilterInternal 验证Header中的token); B --> C(DynamicAuthorityService#hasPermission 验证有没有请求url权限); C --> D(处理逻辑); D --> E(返回结果)

JWTToken拦截器

主要是拦截请求, 验证Header中的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
57
58
59
60
61
package com.liuzhihang.demo.filter;

import com.liuzhihang.demo.utils.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author liuzhihang
* @date 2019-06-05 09:09
*/
@Slf4j
@Component
public class JwtPerTokenFilter extends OncePerRequestFilter {

@Autowired
private JwtTokenUtil jwtTokenUtil;

/**
* 存放Token的Header Key
*/
private static final String HEADER_STRING = "token";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String token = request.getHeader(HEADER_STRING);
if (null != token && !jwtTokenUtil.isTokenExpired(token)) {
UserDetails userDetails = jwtTokenUtil.getUserDetailsFromToken(token);
String username = userDetails.getUsername();

if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

// 通过 username 查询数据库 获取token 然后和库中token作比较

if (username.equals("liuzhihang")) {

UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
filterChain.doFilter(request, response);
}

}

URI动态校验

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 com.liuzhihang.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Set;

/**
* 动态权限认证
*
* @author liuzhihang
* @date 2019-06-25 15:51
*/
@Slf4j
@Component(value = "dynamicAuthorityService")
public class DynamicAuthorityService {


public boolean hasPermission(HttpServletRequest request, Authentication authentication) {

try {
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails && authentication instanceof UsernamePasswordAuthenticationToken) {
// 本次请求的uri
String uri = request.getRequestURI();

// 获取当前用户
UserDetails userDetails = (UserDetails) principal;

String username = userDetails.getUsername();
log.info("本次用户请求认证, username:{}, uri:{}", username, uri);

// 从数据库取逻辑
if (username.equals("liuzhihang")){
Set<String> set = new HashSet<>();
set.add("/homeInfo");
set.add("/getAllUser");
set.add("/editUserInfo");
if (set.contains(uri)) {
return true;
}
}

}
} catch (Exception e) {
log.error("用户请求登录, uri:{} error", request.getRequestURI(), e);
return false;
}
return false;
}
}

测试

脚本在 httpclient脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
POST localhost:8080/login
Content-Type: application/json

{
"username": "liuzhihang",
"password": "123456789"
}
### 请求接口脚本

POST localhost:8080/homeInfo
Content-Type: application/json
token: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsaXV6aGloYW5nIiwiaWF0IjoxNTY5MDI1NjY4LCJleHAiOjE1Njk2MzA0Njh9.Kot_uLnwtcq-t5o4x3V-xBnpf-mKEi7OV2eAfgMCKLk
###

返回:

1
2
3
4
5
6
{
"resultCode": "0000",
"resultMsg": "登录成功",
"resultTime": "20190920191038",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsaXV6aGloYW5nIiwiaWF0IjoxNTY4OTc3ODM4LCJleHAiOjE1Njk1ODI2Mzh9.MAS9VkFdCF3agkCgTtc0VzPMFjY42vFyIvAEzkSeAfs"
}

参考

前后端分离 SpringBoot + SpringSecurity + JWT + RBAC 实现用户无状态请求验证