前面,我们做了Spring Security和JWT的详细介绍,今天,我们就来整合Spring Security 和 JWT……
《Spring Security十万字详解》
《JWT 三万字详解》
话不多说,上代码
安全配置类
package com.uncle.jwt.config;
import com.uncle.jwt.filter.TokenAuthenticationFilter;
import com.uncle.jwt.filter.TokenLoginFilter;
import com.uncle.jwt.handler.UnauthorizedEntryPoint;
import com.uncle.jwt.util.DefaultPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 14:24
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled=true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码管理工具类
*/
@Autowired
private DefaultPasswordEncoder defaultPasswordEncoder;
/**
* 用户服务类
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 配置设置,设置退出的地址和token
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
//未授权处理
.authenticationEntryPoint(new UnauthorizedEntryPoint())
.and().authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable()
.logout().logoutUrl("/logout")
.and()
.addFilter(new TokenLoginFilter(authenticationManager()))
.addFilter(new TokenAuthenticationFilter(authenticationManager())).httpBasic();
}
/**
* 密码处理
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/**
* 配置哪些请求不拦截
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**");
}
}
测试接口
package com.uncle.jwt.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 14:25
*/
@RestController
public class TestController {
@GetMapping("admin")
public String admin() {
return "admin!!!";
}
@GetMapping("api/hello")
public String user() {
return "hello world";
}
/**
* 方法执行前鉴权
*
* @return
*/
@GetMapping("roleAdmin")
@Secured("ROLE_ADMIN")
public String roleAdmin() {
return "roleAdmin!!!";
}
/**
* 方法执行前鉴权
*
* @return
*/
@GetMapping("preAuthorize")
@PostAuthorize("hasAnyRole('ROLE_ADMIN')")
public String preAuthorize() {
System.out.println("preAuthorize…………方法执行前鉴权");
return "preAuthorize!!!";
}
/**
* 方法执行完再鉴权
*
* @return
*/
@GetMapping("postAuthorize")
@PostAuthorize("hasAnyRole('ROLE_USER')")
public String postAuthorize() {
System.out.println("postAuthorize…………方法执行完再鉴权");
return "PostAuthorize!!!";
}
}
登录过滤器
package com.uncle.jwt.filter;
import com.uncle.jwt.util.JwtTokenUtil;
import com.uncle.jwt.util.ResponseUtil;
import com.uncle.jwt.util.Result;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 14:38
*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public TokenLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
try {
String username = req.getParameter("username");
String password = req.getParameter("password");
username = username != null ? username.trim() : "";
password = password != null ? password : "";
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 登录成功
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException {
User user = (User) auth.getPrincipal();
String authrorities = user.getAuthorities().size() > 0 ? user.getAuthorities().toString().replaceAll("(?:\\[|null|\\]| +)", "") : user.getAuthorities().toString();
String token = JwtTokenUtil.createToken(user.getUsername(), authrorities);
HashMap<Object, Object> map = new HashMap<>();
map.put("token", token);
map.put("user", user);
map.put("loginName", user.getUsername());
ResponseUtil.out(response, Result.ok(map));
}
/**
* 登录失败
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(response, Result.error("登录失败"));
}
}
package com.uncle.jwt.filter;
import com.uncle.jwt.util.JwtTokenUtil;
import com.uncle.jwt.util.ResponseUtil;
import com.uncle.jwt.util.Result;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 14:56
*/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
public TokenAuthenticationFilter(AuthenticationManager authManager) {
super(authManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("=================" + request.getRequestURI());
//不需要鉴权
if (request.getRequestURI().indexOf("index") != -1) {
chain.doFilter(request, response);
}
UsernamePasswordAuthenticationToken authentication = null;
try {
authentication = getAuthentication(request);
} catch (Exception e) {
ResponseUtil.out(response, Result.error(e.getMessage()));
}
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
ResponseUtil.out(response, Result.error("鉴权失败"));
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// 获取Token字符串,token 置于 header 里
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
token = request.getParameter("token");
}
if (token != null && !"".equals(token.trim())) {
// 从Token中解密获取用户名
String userName = JwtTokenUtil.getUserNameFromToken(token);
if (userName != null) {
// 从Token中解密获取用户角色
String role = JwtTokenUtil.getUserRoleFromToken(token);
// 将ROLE_XXX,ROLE_YYY格式的角色字符串转换为数组
String[] roles = role.split(",");
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (String s : roles) {
authorities.add(new SimpleGrantedAuthority(s));
}
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
return null;
}
}
处理异常类
package com.uncle.jwt.handler;
import com.uncle.jwt.util.ResponseUtil;
import com.uncle.jwt.util.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 15:19
*/
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, Result.error("未授权统一处理"));
}
}
处理用户登录过程的类
package com.uncle.jwt.service;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 15:11
*/
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//从数据库获取用户信息
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
//封装用户信息,用于认证与密码校验
return new User(username, username, authorities);
}
}
加密处理器
package com.uncle.jwt.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 15:37
*/
@Component
@Slf4j
public class DefaultPasswordEncoder extends BCryptPasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
if (encodedPassword == null || encodedPassword.length() == 0) {
log.error("Empty encoded password");
throw new IllegalArgumentException("encodedPassword is null");
}
return encodedPassword.equals(rawPassword);
}
}
JWT工具类
package com.uncle.jwt.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 16:34
*/
public class JwtTokenUtil {
private static long tokenExpiration = 24 * 60 * 60 * 1000;
private static String tokenSignKey = "123456";
private static String userRoleKey = "userRole";
// public String createToken(String userName) {
// String token = Jwts.builder().setSubject(userName)
// .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
// .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
// return token;
// }
public static String createToken(String userName, String role) {
String token = Jwts.builder().setSubject(userName)
.claim(userRoleKey, role)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
public static String getUserNameFromToken(String token) {
String userName = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return userName;
}
public static String getUserRoleFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
return claims.get(userRoleKey).toString();
}
}
响应有关的类
package com.uncle.jwt.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 15:56
*/
public class ResponseUtil {
public static void out(HttpServletResponse response, Result result) {
ObjectMapper mapper = new ObjectMapper();
PrintWriter writer = null;
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
writer = response.getWriter();
mapper.writeValue(writer, result);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.flush();
writer.close();
}
}
}
}
package com.uncle.jwt.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 16:12
*/
@Data
public class Result implements Serializable {
/**
* 定义jackson对象
*/
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 响应业务状态
*/
private long status;
/**
* 响应消息
*/
private String msg;
/**
* 响应中的数据
*/
private Object data;
public static Result build(Long status, String msg, Object data) {
return new Result(status, msg, data);
}
public static Result ok() {
return new Result(null);
}
public static Result ok(Object data) {
return new Result(data);
}
public static Result unAuth() {
return new Result(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), "");
}
public static Result error() {
return new Result(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage(), null);
}
public static Result error(Object msg) {
return new Result(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage(), msg);
}
public Result() {
}
public static Result build(Integer status, String msg) {
return new Result(ResultCode.UNAUTHORIZED.getCode(), msg, null);
}
public Result(Long status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public Result(Object data) {
this.status = ResultCode.SUCCESS.getCode();
this.msg = ResultCode.SUCCESS.getMessage();
this.data = data;
}
/**
* 将json结果集转化为Result对象
*
* @param jsonData json数据 传的是Result的对象的Json字符串
* @param clazz TaotaoResult中的object类型
* @return
*/
public static Result formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, Result.class);
}
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (clazz != null) {
if (data.isObject()) {
obj = MAPPER.readValue(data.traverse(), clazz);
} else if (data.isTextual()) {
obj = MAPPER.readValue(data.asText(), clazz);
}
}
return build((long) jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
/**
* 没有object对象的转化
*
* @param json
* @return
*/
public static Result format(String json) {
try {
return MAPPER.readValue(json, Result.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Object是集合转化
*
* @param jsonData 传的是Result的对象的Json字符串
* json数据
* @param clazz 集合中的类型
* @return
*/
public static Result formatToList(String jsonData, Class<?> clazz) {
try {
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (data.isArray() && data.size() > 0) {
obj = MAPPER.readValue(data.traverse(),
MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
}
return build((long) jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
}
package com.uncle.jwt.util;
/**
* @program: spring-security-jwt
* @description:
* @author: 步尔斯特
* @create: 2021-08-08 15:33
*/
public enum ResultCode {
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败"),
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
private long code;
private String message;
private ResultCode(long code, String message) {
this.code = code;
this.message = message;
}
public long getCode() {
return code;
}
public String getMessage() {
return message;
}
}
启动类
略…
测试
GET http://localhost:8080/postAuthorize
Accept: application/json
token: eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSykstLc5PK1HSUSotTi0Kys9JBQoG-fu4xju6-Hr66YCZocGuQUAVqRUFSlaGZkYWJhYWBhYGtQAtDsvwRAAAAA.6o-vyj5TmIhh8Q8kDmoBOif203HhvvtVKZrVXERa6osP9dp0zSS9HRG1CP6JGz1DC-tJX2hN_ARCWHAW7egroA
###
GET http://localhost:8080/api/hello
Accept: application/json
Cache-Control: no-cache
###
POST http://localhost:8080/login?username=neusoft&password=neusoft
Accept: */*
Cache-Control: no-cache
###
GET http://localhost:8080/admin
Accept: */*
Cache-Control: no-cache
token: eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSykstLc5PK1HSUSotTi0Kys9JBQoG-fu4xju6-Hr66YCZocGuQUAVqRUFSlaGZkYWJhYWBhYGtQAtDsvwRAAAAA.6o-vyj5TmIhh8Q8kDmoBOif203HhvvtVKZrVXERa6osP9dp0zSS9HRG1CP6JGz1DC-tJX2hN_ARCWHAW7egroA
测试结果
POST http://localhost:8080/login?username=neusoft&password=neusoft
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Content-Length: 521
Date: Sun, 08 Aug 2021 09:58:51 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"status": 200,
"msg": "操作成功",
"data": {
"loginName": "neusoft",
"user": {
"password": null,
"username": "neusoft",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSykstLc5PK1HSUSotTi0Kys9JBQoG-fu4xju6-Hr66YCZocGuQUAVqRUFSlaGZkYWpgbGhsYGtQBhPtZ5RAAAAA.G7XsVdsuWpGqI9Elk9-pwDmRhZExixrcmdZfUP-G2lbVekK6pCP8S519OCv5NBlT8nOGoPr37y_ArHqmtJq6Jg"
}
}
Response code: 200; Time: 355ms; Content length: 513 bytes
GET http://localhost:8080/hello
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Content-Length: 57
Date: Sun, 08 Aug 2021 10:01:01 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"status": 500,
"msg": "操作失败",
"data": "鉴权失败"
}
Response code: 200; Time: 238ms; Content length: 41 bytes
GET http://localhost:8080/admin
HTTP/1.1 200
Set-Cookie: JSESSIONID=5D0735B1D787417D2443933A934E8AD1; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 8
Date: Sun, 08 Aug 2021 10:01:24 GMT
Keep-Alive: timeout=60
Connection: keep-alive
admin!!!
Response code: 200; Time: 328ms; Content length: 8 bytes
Cookies are preserved between requests:
> /Users/uncle/Downloads/spring-security-jwt/.idea/httpRequests/http-client.cookies