一.前言吐槽
刚开始学习,网上找了很多例子,可能我比较小白,踩了很多坑,不知道为什么有些大佬,都不屑于测试下的,直接丢仓库了,下载很多都是跑不起来的。看得我一脸懵逼的。好啦不吐槽了,记录下自己学习的东东,开始自己的表演!
二.代码
**2.1源码地址:**https://github.com/myjsonman/spring-security-kavy.git
2.2代码结构目录:
2.3 在IDEA 创建一个简单的Maven 工程添加pom对应的依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- 加密的 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatisplus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.noggit</groupId>
<artifactId>noggit</artifactId>
<version>0.6</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
核心依赖:
security:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
JWt:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.4 简单的表设计:
设计有点粗超,见谅。
权限表:
CREATE TABLE `user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
`role_str` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
用户表:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_img` varchar(255) DEFAULT NULL,
`updatetime` timestamp NULL DEFAULT NULL,
`status` char(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
角色表:
CREATE TABLE `role` (
`id` int(50) NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`updatetime` timestamp NULL DEFAULT NULL,
`role_desc` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
在这里写下项目的流程:
1.创建一个springboot项目
2.导入springSecurity和jwt 依赖
3.创建实体类
4.创建service层
5.创建mapper层
6.创建UserDetail去实现UserDetails
7.创建JwtUserDetailsServiceImpl去实现UserDetailsService
8.创建用户登录获取权限的拦截器
9.配置config 处理拦截器
10.jwt的工具类生成token
开始Spring boot的一个简单API
2.5 实体
@Data
public class Role {
private Integer id;
private String roleName;
private Timestamp updatetime;
private String roleDesc;
}
@Data
public class User implements Serializable {
private Integer id;
@NotBlank(message = "用户名不能为空")
@Size(min=5, max=20,message = "用户名不能小于5位,大于20位")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min=6,max = 20,message = "密码不能小于6位,大于20位")
private String password;
private String userImg;
private Date updatetime;
private String status;
}
@Data
public class UserRoles {
private Integer id;
private Integer userId;
private Integer roleId;
private String roleStr;
}
2.6 service:
public interface UserService {
/**
* 注册用户
* @return
*/
void register(User user,String str);
/**
* 登陆
* @param username
* @param password
* @return
*/
ResponseUserToken login(String username, String password);
}
2.7 实现类
package com.kavy.springsecuritydemo.service.serviceImp;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.entity.UserDetail;
import com.kavy.springsecuritydemo.entity.UserRoles;
import com.kavy.springsecuritydemo.exception.CustomException;
import com.kavy.springsecuritydemo.mapper.UserMapper;
import com.kavy.springsecuritydemo.mapper.UserRolesMapper;
import com.kavy.springsecuritydemo.result.ResultCode;
import com.kavy.springsecuritydemo.result.ResultJson;
import com.kavy.springsecuritydemo.service.UserService;
import com.kavy.springsecuritydemo.utils.JwtUtils;
import com.kavy.springsecuritydemo.utils.ResponseUserToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.apache.commons.lang.StringUtils;
import java.util.Date;
@Service
public class UserServiceImp implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
private UserRolesMapper userRolesMapper;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void register(User user,String str) {
//查询用户
User oldUser = userMapper.findByUsername(user.getUsername());
if (oldUser != null) {
throw new CustomException(ResultJson.failure(ResultCode.BAD_REQUEST, "用户已存在"));
}
//加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode(user.getPassword()));
user.setUpdatetime(new Date(System.currentTimeMillis()));
user.setStatus("0");
userMapper.insert(user);
if (StringUtils.isNotBlank(str)){
//权限插入
String[] roles = str.split(",");
for (String role : roles) {
//如果原先有绑定权限就删除
// userRolesMapper.deleteById(user.getId());
UserRoles userRoles = new UserRoles();
userRoles.setUserId(user.getId());
userRoles.setRoleId(Integer.parseInt(role));
userRoles.setRoleStr(str);
userRolesMapper.insert(userRoles);
}
}
}
@Override
public ResponseUserToken login(String username, String password) {
//用户验证
final Authentication authentication = authenticate(username, password);
//存储认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);
//生成token ,查看源代码会发现调用getPrincipal()方法会返回一个实现了`UserDetails`接口的对象
final UserDetail userDetail = (UserDetail) authentication.getPrincipal();
//通过工具类生成token
final String token = "Bearer "+jwtUtils.generateAccessToken(userDetail);
//存储token
jwtUtils.putToken(username, token);
// 学习 测试用,把用户的信息也返回了
return new ResponseUserToken(token, userDetail);
}
private Authentication authenticate(String username, String password) {
try {
//该方法会去调用userDetailsService.loadUserByUsername()去验证用户名和密码,如果正确,则存储该用户名密码到“security 的 context中”
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException | BadCredentialsException e) {
throw new CustomException(ResultJson.failure(ResultCode.LOGIN_ERROR, e.getMessage()));
}
}
}
2.8 Mapper
注:说是MybatisPlus 其实就是保存数据的时候用了,不过我已经整合好了,大家Mybatis 和Plus 两个可以无缝切换使用 。 BaseMapper<> 这个是MybatisPlus 的写法;
RoleMapper:
package com.kavy.springsecuritydemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kavy.springsecuritydemo.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface RoleMapper extends BaseMapper<Role> {
/**
* 创建用户角色
* @param userId
* @param roleId
* @return
*/
int insertRole(long userId, long roleId);
/**
* 根据角色id查找角色
* @param roleId
* @return
*/
Role findRoleById(long roleId);
/**
* 根据用户id查找该用户角色
* @param userId
* @return
*/
List<Role> findRoleByUserId(@Param("userId") Integer userId);
}
UserMapper:
@Mapper
public interface UserMapper extends BaseMapper<User> {
User findByUsername(String username);
}
UserRolesMapper:
public interface UserRolesMapper extends BaseMapper<UserRoles> {
}
roleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kavy.springsecuritydemo.mapper.RoleMapper">
<insert id="insertRole">
insert into user_roles (user_id, role_id) VALUES (#{userId}, #{roleId});
</insert>
<select id="findRoleById" resultType="Role">
select id, role_name, role_desc from role where id = #{roleId}
</select>
<select id="findByUsername" parameterType="String" resultType="User">
SELECT id, username, password from user where username = #{username};
</select>
<select id="findRoleByUserId" resultType="Role">
select * from role where id in (SELECT role_id from user_roles where user_id = #{userId});
</select>
</mapper>
userMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kavy.springsecuritydemo.mapper.UserMapper">
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into user (username, password,updatetime,status) VALUES (#{username}, #{password},#{updatetime},#{status});
</insert>
<select id="findByUsername" parameterType="String" resultType="User">
SELECT id, username, password from user where username = #{username};
</select>
<select id="queryByUsername" parameterType="String" resultType="User">
SELECT id, username, password from user where username = #{username};
</select>
</mapper>
Controller:
package com.kavy.springsecuritydemo.controller;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.result.ResultCode;
import com.kavy.springsecuritydemo.result.ResultJson;
import com.kavy.springsecuritydemo.service.UserService;
import com.kavy.springsecuritydemo.utils.ResponseUserToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
UserService userService;
@GetMapping("/hello")
public String hello(){
return "hi security!";
}
/**
* 注册
* @param user
*/
@PostMapping("/register")
public ResultJson signUp( User user ,String str) {
if (user==null){
ResultJson.failure(ResultCode.BAD_REQUEST);
}
userService.register(user,str);
return ResultJson.success();
}
/**
* 获取token
* @param user
* @return
*/
@PostMapping("/login")
public ResultJson<ResponseUserToken> login(@RequestBody User user) {
final ResponseUserToken response = userService.login(user.getUsername(), user.getPassword());
return ResultJson.ok(response);
}
}
标题党
卧槽 发现贴得有点啰嗦,Security 和JWT呢。别急大佬,别喷,马上=。=上代码
网咯配图(咱也画不出来,也不敢问,都是围观大佬的):
1.开始整合JWT认证
创建一个JwtUserDetailsServiceImpl 去实现 UserDetailsService接口,UserDetailsService接口就一个方法 loadUserByUsername()
package com.kavy.springsecuritydemo.service.serviceImp;
import com.kavy.springsecuritydemo.entity.Role;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.entity.UserDetail;
import com.kavy.springsecuritydemo.mapper.RoleMapper;
import com.kavy.springsecuritydemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.AuthorityUtils;
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 java.util.List;
@Configuration
public class JwtUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No userDetail found with username '%s'.", username));
}
//查询权限封装
List<Role> roleByUserId = roleMapper.findRoleByUserId(user.getId());
return new UserDetail(user.getUsername(),roleByUserId,user.getPassword());
}
}
再创建UserDetail 去实现UserDetails 接口,JWT通过这个接口去获取用户密码和权限
package com.kavy.springsecuritydemo.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@Data
public class UserDetail implements UserDetails {
private long id;
private String username;
private String password;
private List<Role> roles;
//private Collection<? extends GrantedAuthority> authorities;
public UserDetail(
String username,
List<Role> roles,
String password) {
this.username = username;
this.password = password;
this.roles = roles;
}
//getAuthorities获取用户包含的权限,返回权限集合,权限是一个继承了GrantedAuthority的对象;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 根据自定义逻辑来返回用户权限,如果用户权限返回空或者和拦截路径对应权限不同,验证不通过
if (!roles.isEmpty()){
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
return authorities;
}
return null;
}
//getPassword和getUsername用于获取密码和用户名;
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
//isAccountNonExpired方法返回boolean类型,用于判断账户是否未过期,未过期返回true反之返回false;
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
//isAccountNonLocked方法用于判断账户是否未锁定;
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
//isCredentialsNonExpired用于判断用户凭证是否没过期,即密码是否未过期;
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//isEnabled方法用于判断用户是否可用。
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
拦截器filter
package com.kavy.springsecuritydemo.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kavy.springsecuritydemo.service.serviceImp.JwtUserDetailsServiceImpl;
import com.kavy.springsecuritydemo.utils.JwtUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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 io.jsonwebtoken.ExpiredJwtException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsServiceImpl jwtUserDetailsService;
@Autowired
private JwtUtils jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
//获取header 的Authorization
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT报文表头的格式是"Bearer token". 去除"Bearer ",直接获取token
// only the Token
if (StringUtils.isNotEmpty(requestTokenHeader) && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
//获取username
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//获取 userDetails
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
错误返回:
package com.kavy.springsecuritydemo.filter;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/**
* 用来解决匿名用户访问无权限资源时的异常
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = 1L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
核心配置config:
package com.kavy.springsecuritydemo.config;
import com.kavy.springsecuritydemo.filter.JwtAuthenticationEntryPoint;
import com.kavy.springsecuritydemo.filter.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @EnableWebSecurity注解继承WebSecurityConfigurerAdapter的类,这样就构成了Spring Security的配置。
*
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private JwtRequestFilter jwtRequestFilter;
public WebSecurityConfig(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
//用来解决匿名用户访问无权限资源时的异常
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
//禁用session 无状态
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// dont authenticate this particular request
.and()
.authorizeRequests()
//注册 register 和登录 login 不需要验证 就可以访问,其他的都需要token验证才可以访问
.antMatchers(HttpMethod.POST, "/api/login", "/api/register", "/error/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity
.headers()
.frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
/* @Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 表单方式
// http.httpBasic() // HTTP Basic方式
.loginPage("/login.html") //.loginPage("/login.html")指定了跳转到登录页面的请求URL
.loginProcessingUrl("/login") //.loginProcessingUrl("/login")对应登录页面form表单的action="/login"
.and()
.authorizeRequests() // 授权配置
.antMatchers("/login.html").permitAll() // 表示跳转到登录页面的请求不被拦截,否则会进入无限循环。
.anyRequest() // 所有请求
.authenticated() // 都需要认证
.and().csrf().disable();
}*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JWT的工具类:
用来生成token的
package com.kavy.springsecuritydemo.utils;
import com.kavy.springsecuritydemo.entity.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class JwtUtils {
private static final String CLAIM_KEY_USER_ID = "user_id";
private static final String CLAIM_KEY_AUTHORITIES = "scope";
private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
//密匙
@Value("${jwt.secret}")
private String secret;
//有效期
@Value("${jwt.expiration}")
private Long access_token_expiration;
//刷新密匙
@Value("${jwt.expiration}")
private Long refresh_token_expiration;
private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
//获取token
public String generateAccessToken(UserDetail userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
return generateAccessToken(userDetail.getUsername(), claims);
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public void putToken(String userName, String token) {
tokenMap.put(userName, token);
}
/**
* 生成token的过期时间
* @return
*/
private Date generateExpirationDate(long expiration) {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取claims
*
**/
public Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
/**
* 获取用户名
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 获取token 过期时间
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
/**
* 判断token 是否过期
* @param token
* @return
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 判断token是否可以刷新
* @param token
* @return
*/
public Boolean canTokenBeRefreshed(String token) {
final Date created = getCreatedDateFromToken(token);
return !isTokenExpired(token);
}
/**
* 从token中获取创建时间
* @param token
* @return
*/
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = claims.getIssuedAt();
} catch (Exception e) {
created = null;
}
return created;
}
/**
* 刷新token
* @param token
* @return
*/
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
refreshedToken = generateAccessToken(claims.getSubject(), claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* claims
* @param userDetail
* @return
*/
private Map<String, Object> generateClaims(UserDetail userDetail) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
System.out.println("userDetail.getId()--------->>>"+userDetail.getId());
System.out.println("userDetail--->"+userDetail.toString());
return claims;
}
// 传入 username 过期时间 claims 获取token
private String generateAccessToken(String subject, Map<String, Object> claims) {
// subject --->userDetail.getUsername() access_token_expiration过期时间
return generateToken(subject, claims, access_token_expiration);
}
private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
List<String> list = new ArrayList<>();
for (GrantedAuthority ga : authorities) {
list.add(ga.getAuthority());
}
return list;
}
/**
* 生成token
* @param subject
* @param claims
* @param expiration
* @return
*/
private String generateToken(String subject, Map<String, Object> claims, long expiration) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject) //username 设置面向用户
// .setId(UUID.randomUUID().toString())
.setIssuedAt(new Date()) //签发时间
.setExpiration(generateExpirationDate(expiration)) //生成token的过期时间
.compressWith(CompressionCodecs.DEFLATE)
.signWith(SIGNATURE_ALGORITHM, secret) //SignatureAlgorithm.HS512, secret 生成签名
.compact();
}
}
整个Security就完成了,在此我只做了注册和获取token的两个,嗯,还有一些返回的封装和错误码返回处理,没有贴出来,具体可以去看下源码,其他的可以根据自己需求写了;
测试:
没有获取到token,所以会报错
注册:
注册成功,通过注册的用户和密码获取token
通过获取到的token,再次访问hello接口