赞
踩
JWT 全称 JSON Web Token,JWT 主要用于用户登录鉴权,当用户登录之后,返回给前端一个Token,之后用户利用Token进行信息交互。
除了JWT认证之外,比较传统的还有Session认证,如何选择可以查看之前的博文:基于token与Session身份认证对比-CSDN博客简单来说认证就是让服务器知道你是谁?就是让服务器知道你能干什么,不能干什么?那么基于身份认证我们一般有俩种方式:Session-Cookie和JWT。https://blog.csdn.net/shaogaiyue9745602/article/details/135130114?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22135130114%22%2C%22source%22%3A%22shaogaiyue9745602%22%7D
Spring Security 是基于 Spring 的身份认证(Authentication)和用户授权(Authorization)框架,提供了一套 Web 应用安全性的完整解决方案。其中核心技术使用了 Servlet 过滤器、IOC 和 AOP 等。实际操作时经常需要实现XXXFilter来自定义的登录以及访问控制。
身份认证指的是用户去访问系统资源时,系统要求验证用户的身份信息,用户身份合法才访问对应资源。常见的身份认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
当身份认证通过后,去访问系统的资源,系统会判断用户是否拥有访问该资源的权限,只允许访问有权限的系统资源,没有权限的资源将无法访问,这个过程叫用户授权。比如 会员管理模块有增删改查功能,有的用户只能进行查询,而有的用户可以进行修改、删除。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
其中认证的过程还应包含 鉴权 以及 token过期 验证。
代码实现前需要有springboot项目有基础的用户user表,并可以实现数据的查询功能。最好有自己的统一返回配置和全局异常处理配置,若没有可参考前面的博客参考进行配置或自行配置。
- <!--安全框架引入, 进行权限控制-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
-
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.50</version>
- </dependency>
-
- <dependency>
- <groupId>javax.xml.bind</groupId>
- <artifactId>jaxb-api</artifactId>
- <version>2.3.1</version>
- </dependency>
引入依赖重新启动后,访问项目发现会出现登录页面如下:
用户名默认为:user
默认密码在项目启动时会打印在控制台如下:
也可通过在配置文件 application.properties 自定义用户名和密码
- spring.security.user.name=admin
- spring.security.user.password=admin
- package com.hng.config.jwtSecurity;
-
- import com.alibaba.fastjson.annotation.JSONField;
- import com.hng.entity.SysUser;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
-
- import java.util.Collection;
- import java.util.List;
-
- /**
- * @Author: 郝南过
- * @Description:
- * @Date: 2023/11/29 10:26
- * @Version: 1.0
- */
- @Data
- @NoArgsConstructor
- public class SecurityUser implements UserDetails {
-
- private SysUser user;
-
- private List<String> permissions;
-
- @JSONField(serialize = false) // 防止存入redis时序列化出错,不进行序列化,也不存入redis中
- private List<SimpleGrantedAuthority> authorities;
-
- public SecurityUser(SysUser user) {
- this.user = user;
- }
-
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- return null;
- }
-
- @Override
- public String getPassword() {
- return user.getPassword();
- }
-
- @Override
- public String getUsername() {
- return user.getUserName();
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
该类主要功能是将Security拦截的用户名密码改为查询数据库中的用户名密码,以及权限的相关认证(权限认证相关本篇暂时不做添加)
- package com.hng.config.jwtSecurity;
-
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.hng.entity.SysUser;
- import com.hng.service.SysUserService;
- 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.stereotype.Service;
-
- import javax.annotation.Resource;
- import java.util.Objects;
-
- /**
- * @Author: 郝南过
- * @Description: 将Security拦截的用户名密码改为数据库中已有的用户名密码,Security自己校验密码,默认使用PasswordEncoder,格式为{id}password(id代表加密方式),
- * 一般不采用此方式,SpringSecurity提供了BcryptPasswordEncoder,只需将此注入到spring容器中。SpringSecurity就会使用它进行替换校验
- * @Date: 2023/12/11 11:29
- * @Version: 1.0
- */
- @Service
- @Slf4j
- public class UserDetailsServiceImpl implements UserDetailsService {
-
- @Resource
- private SysUserService sysUserService;
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- //查询用户信息
- LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper();
- queryWrapper.eq(SysUser::getUserName,username);
- SysUser user = sysUserService.getOne(queryWrapper);
- if(Objects.isNull(user)){
- throw new RuntimeException("用户名或密码错误");
- }
- //TODO 查询授权信息
- return new SecurityUser(user);
- }
- }
该工具类用于token的创建验证等
- package com.hng.config.jwtSecurity;
-
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.stereotype.Component;
-
- import javax.servlet.http.HttpServletRequest;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * @Description JwtToken生成的工具类
- * @Version 1.0
- **/
- @Slf4j
- @Component
- public class JwtTokenUtil {
-
- private static final String CLAIM_KEY_USERNAME = "username";
- private static final String CLAIM_KEY_CREATED = "created";
- private static final String CLAIM_KEY_USER_ID = "userId";
-
- // 令牌自定义标识
- @Value("${jwt.token.header}")
- private String header;
-
- // 令牌秘钥
- @Value("${jwt.token.secret}")
- private String secret;
-
- // 令牌有效期(默认30分钟),也可将token的过期时间交给redis管理
- @Value("${jwt.token.expireTime}")
- private Long expiration;
-
-
- /**
- * 根据负责生成JWT的token
- */
- private String generateToken(Map<String, Object> claims) {
- return Jwts.builder()
- .setClaims(claims)
- .setExpiration(generateExpirationDate())
- .signWith(SignatureAlgorithm.HS512, secret)
- .compact();
- }
-
- /**
- * 从token中获取JWT中的负载
- */
- public Claims getClaimsFromToken(String token) {
- Claims claims = null;
- try {
- claims = Jwts.parser()
- .setSigningKey(secret)
- .parseClaimsJws(token)
- .getBody();
- } catch (Exception e) {
- log.info("JWT格式验证失败:{}",token);
- }
- return claims;
- }
-
- /**
- * 生成token的过期时间,返回Date
- */
- private Date generateExpirationDate() {
- return new Date(System.currentTimeMillis() + expiration * 1000);
- }
-
- /**
- * 从token中获取登录用户名
- */
- public String getUserNameFromToken(String token) {
- String username;
- try {
- Claims claims = getClaimsFromToken(token);
- username = claims.getSubject();
- } catch (Exception e) {
- username = null;
- }
- return username;
- }
-
- /**
- * 验证token是否还有效
- *
- * @param token 客户端传入的token
- * @param userDetails 从数据库中查询出来的用户信息
- */
- public boolean validateToken(String token, UserDetails userDetails) {
- String username = getUserNameFromToken(token);
- return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
- }
-
- /**
- * 判断token是否已经失效
- */
- public boolean isTokenExpired(String token) {
- Date expiredDate = getExpiredDateFromToken(token);
- return expiredDate.before(new Date());
- }
-
- /**
- * 从token中获取过期时间
- */
- private Date getExpiredDateFromToken(String token) {
- Claims claims = getClaimsFromToken(token);
- return claims.getExpiration();
- }
-
- /**
- * 根据用户信息生成token
- */
- public String generateToken(UserDetails userDetails) {
- Map<String, Object> claims = new HashMap<>();
- claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
- claims.put(CLAIM_KEY_CREATED, new Date());
- return generateToken(claims);
- }
-
- /**
- * 根据用户信息生成token
- */
- public String generateToken(SecurityUser securityUser) {
- Map<String, Object> claims = new HashMap<>();
- claims.put(CLAIM_KEY_USERNAME, securityUser.getUsername());
- claims.put(CLAIM_KEY_USER_ID, securityUser.getUser().getId());
- claims.put(CLAIM_KEY_CREATED, new Date());
- return generateToken(claims);
- }
-
- /**
- * 判断token是否可以被刷新
- */
- public boolean canRefresh(String token) {
- return !isTokenExpired(token);
- }
-
- /**
- * 刷新token
- */
- public String refreshToken(String token) {
- Claims claims = getClaimsFromToken(token);
- claims.put(CLAIM_KEY_CREATED, new Date());
- return generateToken(claims);
- }
-
- /**
- * 获取请求token
- *
- * @param request
- * @return token
- */
- public String getToken(HttpServletRequest request)
- {
- return request.getHeader(header);
- }
- }
springboot如何集成redis可查看之前的博文
记得在配置文件application.properties中添加redis配置信息
- spring.redis.host=127.0.0.1
- spring.redis.host.port=6379
- #spring.redis.host.name=
- #spring.redis.host.password=
RedisUtil.java
- package com.hng.config.redis;
-
- import org.springframework.core.annotation.Order;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
-
- import javax.annotation.Resource;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.TimeUnit;
-
- @Component
- @Order(-1)
- public final class RedisUtil {
-
- @Resource
- private RedisTemplate redisTemplate;
-
- /**
- * 指定缓存失效时间
- *
- * @param key 键
- * @param time 时间(秒)
- * @return 0
- */
- public boolean expire(String key, long time) {
- try {
- if (time > 0) {
- redisTemplate.expire(key, time, TimeUnit.SECONDS);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 根据key 获取过期时间
- *
- * @param key 键 不能为null
- * @return 时间(秒) 返回0代表为永久有效
- */
- public long getExpire(String key) {
- return redisTemplate.getExpire(key, TimeUnit.SECONDS);
- }
-
- /**
- * 判断key是否存在
- *
- * @param key 键
- * @return true 存在 false不存在
- */
- public boolean hasKey(String key) {
- try {
- return redisTemplate.hasKey(key);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 删除缓存
- *
- * @param key 可以传一个值 或多个
- */
- @SuppressWarnings("unchecked")
- public void del(String... key) {
-
- if (key != null && key.length > 0) {
- if (key.length == 1) {
- redisTemplate.delete(key[0]);
- } else {
- redisTemplate.delete(CollectionUtils.arrayToList(key));
- }
- }
- }
-
- // ============================String=============================
-
- /**
- * 普通缓存获取
- *
- * @param key 键
- * @return 值
- */
- public Object get(String key) {
- return key == null ? null : redisTemplate.opsForValue().get(key);
- }
-
- /**
- * 普通缓存放入
- *
- * @param key 键
- * @param value 值
- * @return true成功 false失败
- */
- public boolean set(String key, Object value) {
- try {
- redisTemplate.opsForValue().set(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 普通缓存放入并设置时间
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
- * @return true成功 false 失败
- */
- public boolean set(String key, Object value, long time) {
- try {
- if (time > 0) {
- redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
- } else {
- set(key, value);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
-
- /**
- * 递增
- *
- * @param key 键
- * @param delta 要增加几(大于0)
- * @return
- */
- public long incr(String key, long delta) {
-
- if (delta < 0) {
- throw new RuntimeException("递增因子必须大于0");
- }
- return redisTemplate.opsForValue().increment(key, delta);
- }
-
-
- /**
- * 递减
- *
- * @param key 键
- * @param delta 要减少几(小于0)
- * @return
- */
- public long decr(String key, long delta) {
-
- if (delta < 0) {
- throw new RuntimeException("递减因子必须大于0");
- }
- return redisTemplate.opsForValue().increment(key, -delta);
- }
-
- // ================================Map=================================
-
- /**
- * HashGet
- *
- * @param key 键 不能为null
- * @param item 项 不能为null
- * @return 值
- */
- public Object hget(String key, String item) {
- return redisTemplate.opsForHash().get(key, item);
- }
-
-
- /**
- * 获取hashKey对应的所有键值
- *
- * @param key 键
- * @return 对应的多个键值
- */
- public Map<Object, Object> hmget(String key) {
- return redisTemplate.opsForHash().entries(key);
- }
-
- /**
- * HashSet
- *
- * @param key 键
- * @param map 对应多个键值
- * @return true 成功 false 失败
- */
- public boolean hmset(String key, Map<String, Object> map) {
- try {
- redisTemplate.opsForHash().putAll(key, map);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * HashSet 并设置时间
- *
- * @param key 键
- * @param map 对应多个键值
- * @param time 时间(秒)
- * @return true成功 false失败
- */
- public boolean hmset(String key, Map<String, Object> map, long time) {
- try {
- redisTemplate.opsForHash().putAll(key, map);
- if (time > 0) {
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 向一张hash表中放入数据,如果不存在将创建
- *
- * @param key 键
- * @param item 项
- * @param value 值
- * @return true 成功 false失败
- */
- public boolean hset(String key, String item, Object value) {
- try {
- redisTemplate.opsForHash().put(key, item, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 向一张hash表中放入数据,如果不存在将创建
- * 0
- *
- * @param key 键
- * @param item 项
- * @param value 值
- * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
- * @return true 成功 false失败
- */
- public boolean hset(String key, String item, Object value, long time) {
- try {
- redisTemplate.opsForHash().put(key, item, value);
- if (time > 0) {
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 删除hash表中的值
- *
- * @param key 键 不能为null
- * @param item 项 可以使多个 不能为null
- */
- public void hdel(String key, Object... item) {
- redisTemplate.opsForHash().delete(key, item);
- }
-
- /**
- * 判断hash表中是否有该项的值
- *
- * @param key 键 不能为null
- * @param item 项 不能为null
- * @return true 存在 false不存在
- */
- public boolean hHasKey(String key, String item) {
- return redisTemplate.opsForHash().hasKey(key, item);
- }
-
- /**
- * hash递增 如果不存在,就会创建一个 并把新增后的值返回
- *
- * @param key 键
- * @param item 项
- * @param by 要增加几(大于0)
- * @return
- */
- public double hincr(String key, String item, double by) {
- return redisTemplate.opsForHash().increment(key, item, by);
- }
-
- /**
- * hash递减
- *
- * @param key 键
- * @param item 项
- * @param by 要减少记(小于0)
- * @return
- */
- public double hdecr(String key, String item, double by) {
- return redisTemplate.opsForHash().increment(key, item, -by);
- }
-
- // ============================set=============================
-
- /**
- * 根据key获取Set中的所有值
- *
- * @param key 键
- * @return
- */
- public Set<Object> sGet(String key) {
- try {
- return redisTemplate.opsForSet().members(key);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * 根据value从一个set中查询,是否存在
- *
- * @param key 键
- * @param value 值
- * @return true 存在 false不存在
- */
- public boolean sHasKey(String key, Object value) {
-
- try {
- return redisTemplate.opsForSet().isMember(key, value);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将数据放入set缓存
- *
- * @param key 键
- * @param values 值 可以是多个
- * @return 成功个数
- */
- public long sSet(String key, Object... values) {
- try {
- return redisTemplate.opsForSet().add(key, values);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 将set数据放入缓存
- *
- * @param key 键
- * @param time 时间(秒)
- * @param values 值 可以是多个
- * @return 成功个数
- */
- public long sSetAndTime(String key, long time, Object... values) {
-
- try {
- Long count = redisTemplate.opsForSet().add(key, values);
- if (time > 0)
- expire(key, time);
- return count;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 获取set缓存的长度
- *
- * @param key 键
- * @return
- */
- public long sGetSetSize(String key) {
- try {
- return redisTemplate.opsForSet().size(key);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
-
- /**
- * 移除值为value的
- *
- * @param key 键
- * @param values 值 可以是多个
- * @return 移除的个数
- */
- public long setRemove(String key, Object... values) {
- try {
- Long count = redisTemplate.opsForSet().remove(key, values);
- return count;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- // ===============================list=================================
-
- /**
- * 获取list缓存的内容
- *
- * @param key 键
- * @param start 开始
- * @param end 结束 0 到 -代表所有值
- * @return
- */
- public List<Object> lGet(String key, long start, long end) {
-
- try {
- return redisTemplate.opsForList().range(key, start, end);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * 获取list缓存的长度
- *
- * @param key 键
- * @return 0
- */
- public long lGetListSize(String key) {
- try {
- return redisTemplate.opsForList().size(key);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 通过索引 获取list中的值
- *
- * @param key 键
- * @param index 索引 index>=0时, 0 表头, 第二个元素,依次类推;index<0时,-,表尾,-倒数第二个元素,依次类推
- * @return 0
- */
- public Object lGetIndex(String key, long index) {
- try {
- return redisTemplate.opsForList().index(key, index);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @return
- */
- public boolean lSet(String key, Object value) {
- try {
- redisTemplate.opsForList().rightPush(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒)
- * @return
- */
- public boolean lSet(String key, Object value, long time) {
- try {
- redisTemplate.opsForList().rightPush(key, value);
- if (time > 0)
- expire(key, time);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @return
- */
- public boolean lSet(String key, List<Object> value) {
- try {
- redisTemplate.opsForList().rightPushAll(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒)
- * @return 0
- */
- public boolean lSet(String key, List<Object> value, long time) {
- try {
- redisTemplate.opsForList().rightPushAll(key, value);
- if (time > 0)
- expire(key, time);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 根据索引修改list中的某条数据
- *
- * @param key 键
- * @param index 索引
- * @param value 值
- * @return 0
- */
- public boolean lUpdateIndex(String key, long index, Object value) {
- try {
- redisTemplate.opsForList().set(key, index, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- /**
- * 移除N个值为value
- *
- * @param key 键
- * @param count 移除多少个
- * @param value 值
- * @return 移除的个数
- */
- public long lRemove(String key, long count, Object value) {
- try {
- Long remove = redisTemplate.opsForList().remove(key, count, value);
- return remove;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- }
-
该类为登录授权过滤器
- package com.hng.config.jwtSecurity;
-
- import com.hng.config.redis.RedisUtil;
- import io.jsonwebtoken.Claims;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.stereotype.Component;
- import org.springframework.util.StringUtils;
- import org.springframework.web.filter.OncePerRequestFilter;
- import org.springframework.web.servlet.HandlerExceptionResolver;
-
- import javax.annotation.Resource;
- 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.Objects;
-
- /**
- * @Author: 郝南过
- * @Description: JWT 登录授权过滤器
- * @Date: 2023/11/28 16:19
- * @Version: 1.0
- */
- @Component
- public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
-
- @Resource
- private RedisUtil redisUtil;
-
- // JWT 工具类
- @Resource
- private JwtTokenUtil jwtTokenUtil;
-
- @Resource
- @Qualifier("handlerExceptionResolver")
- private HandlerExceptionResolver resolver;
-
- /**
- * 从请求中获取 JWT 令牌,并根据令牌获取用户信息,最后将用户信息封装到 Authentication 中,
- * 方便后续校验(只会执行一次)
- * @param request
- * @param response
- * @param filterChain
- * @throws ServletException
- * @throws IOException
- */
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- //获取token
- String token = request.getHeader("token");
- if (!StringUtils.hasText(token)) {
- //token为空的话, 就不管它, 让SpringSecurity中的其他过滤器处理请求,请求放行
- filterChain.doFilter(request, response);
- return;
- }
- //token不为空时, 解析token
- String userId = null;
- try {
- Claims claims = jwtTokenUtil.getClaimsFromToken(token);
- //解析出userId
- userId = claims.get("userId", String.class);
- } catch (Exception e) {
- e.printStackTrace();
- // 交给全局异常处理类处理
- throw new RuntimeException("token非法");
- // resolver.resolveException(request, response, null,new RuntimeException("token非法"));
- }
- //使用userId从Redis缓存中获取用户信息
- String redisKey = "login:" + userId;
- SecurityUser securityUser = (SecurityUser)redisUtil.get(redisKey);
- if (Objects.isNull(securityUser)) {
- throw new RuntimeException("用户未登录");
- // 交给全局异常处理类处理
- // resolver.resolveException(request, response, null,new RuntimeException("用户未登录"));
- }
- //将用户安全信息存入SecurityContextHolder, 在之后SpringSecurity的过滤器就不会拦截
- UsernamePasswordAuthenticationToken authenticationToken =
- new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- //放行
- filterChain.doFilter(request, response);
- }
- }
该类主要为Security配置类
- package com.hng.config.jwtSecurity;
-
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.authentication.AuthenticationManager;
- 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.WebSecurityConfigurerAdapter;
- import org.springframework.security.config.http.SessionCreationPolicy;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
- import javax.annotation.Resource;
-
- /**
- * @Author: 郝南过
- * @Description: TODO
- * @Date: 2023/11/28 16:24
- * @Version: 1.0
- */
- @Configuration //注册为SpringBoot的配置类
- @Slf4j
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- //注入Jwt认证拦截器.
- @Resource
- private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
-
- /**
- * 将BCryptPasswordEncoder加密器注入SpringSecurity中,
- * SpringSecurity的DaoAuthenticaionProvider会调用该加密器中的match()方法进行密码比对, 密码比对过程不需要我们干涉
- * @return
- */
- @Bean
- public BCryptPasswordEncoder bcryptPasswordBean(){
- return new BCryptPasswordEncoder();
- }
-
- /**
- * 注入身份验证管理器, 直接继承即可.
- * @return
- * @throws Exception
- */
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- /**
- * 配置
- * @param http
- * @throws Exception
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- //禁用跨站请求伪造
- .csrf().disable()
- //禁用Session,使用token作为信息传递介质
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- //把token校验过滤器添加到过滤器链中, 添加在UsernamePasswordAuthenticationFilter之前是因为只要用户携带token,
- //就不需要再去验证是否有用户名密码了 (而且我们不使用表单登入, UsernamePasswordAuthenticationFilter是无法解析Json的, 相当于它没用了)
- //UsernamePasswordAuthenticationFilter是SpringSecurity默认配置的表单登录拦截器
- .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
- // 认证请求的配置
- .authorizeRequests()
- // 将登入和注册的接口放开
- .antMatchers("/sys/login").anonymous()
- .antMatchers("/sys/register").anonymous()
- //除了上面的那些, 剩下的任何接口请求都需要经过认证
- .anyRequest().authenticated()
- .and()
- //允许跨域请求
- .cors();
- }
-
- }
- # 令牌自定义标识
- jwt.token.header=token
- # 令牌秘钥
- jwt.token.secret=ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
- # 令牌有效期(默认30分钟)
- jwt.token.expireTime=1800
- package com.hng.controller;
-
- import com.hng.config.redis.RedisUtil;
- import com.hng.config.jwtSecurity.JwtTokenUtil;
- import com.hng.config.jwtSecurity.SecurityUser;
- import com.hng.entity.SysUser;
- import com.hng.service.SysUserService;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.web.bind.annotation.*;
-
- import javax.annotation.Resource;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * @Author: 郝南过
- * @Description: TODO
- * @Date: 2023/11/29 10:20
- * @Version: 1.0
- */
- @Slf4j
- @RestController
- @RequestMapping("/sys/")
- @Api(value = "LoginController", tags = "LoginController")
- public class LoginController {
-
- @Resource
- private PasswordEncoder passwordEncoder;
- @Resource
- private AuthenticationManagerBuilder authenticationManagerBuilder;
- @Resource
- private JwtTokenUtil jwtTokenUtil;
- @Resource
- private RedisUtil redisUtil;
- @Resource
- private SysUserService sysUserService;
-
- @ApiOperation("用户登录")
- @PostMapping("/login")
- @ResponseBody
- public ResponseEntity<Object> userLogin(@RequestBody SysUser user) {
-
- UsernamePasswordAuthenticationToken authenticationToken =
- new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
- Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
- SecurityContextHolder.getContext().setAuthentication(authentication);
- //生成token
- SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
- String userId = securityUser.getUser().getId();
- String jwtToken = jwtTokenUtil.generateToken(securityUser);
- //redis保存用户信息
- boolean resultRedis = redisUtil.set("login:" + userId, securityUser);
- if(!resultRedis){
- throw new RuntimeException("redis连接不上,登录失败");
- }
- // 返回 token 与 用户信息
- Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
- put("token", jwtToken);
- put("user", securityUser);
- }};
- return ResponseEntity.ok(authInfo);
- }
-
- @ApiOperation("用户注册")
- @PostMapping("/register")
- @ResponseBody
- public ResponseEntity<Object> userRegister(@RequestBody SysUser user){
- user.setPassword(passwordEncoder.encode("123456"));
- sysUserService.addSysUser(user);
- return new ResponseEntity<>(HttpStatus.CREATED);
- }
-
- @ApiOperation("退出登录")
- @GetMapping(value = "/logout")
- public ResponseEntity<Object> logout() {
- UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
- SecurityUser securityUser = (SecurityUser)authenticationToken.getPrincipal();
- redisUtil.del("login:" + securityUser.getUser().getId());
- return new ResponseEntity<>(HttpStatus.OK);
- }
- }
-
使用postman进行测试
将login中获取的token复制到headers中,请求测试
到此登录拦截已经可以正常使用了
该类主要用来处理认证失败异常
- package com.hng.config.security;
-
- import com.alibaba.fastjson.JSONObject;
- import com.hng.config.response.ResponseResult;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.http.HttpStatus;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.web.AuthenticationEntryPoint;
- import org.springframework.stereotype.Component;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- /**
- * @Author: 郝南过
- * @Description: 认证失败处理器
- * @Date: 2023/12/19 17:17
- * @Version: 1.0
- */
- @Component
- @Slf4j
- public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
- @Override
- public void commence(HttpServletRequest request,
- HttpServletResponse response,
- AuthenticationException authException) throws IOException {
- // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
- response.setContentType("application/json;charset=UTF-8");
- response.setStatus(HttpStatus.OK.value());
- response.getWriter().write(JSONObject.toJSONString(ResponseResult.fail(HttpStatus.UNAUTHORIZED.value(),"认证失败")));
- response.getWriter().flush();
- }
- }
该类主要用于处理鉴权失败异常
- package com.hng.config.security;
-
- import com.alibaba.fastjson.JSONObject;
- import com.hng.config.response.ResponseResult;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.http.HttpStatus;
- import org.springframework.security.access.AccessDeniedException;
- import org.springframework.security.web.access.AccessDeniedHandler;
- import org.springframework.stereotype.Component;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- /**
- * @Author: 郝南过
- * @Description: 鉴权失败处理
- * @Date: 2023/12/19 17:26
- * @Version: 1.0
- */
- @Component
- @Slf4j
- public class JwtAccessDeniedHandler implements AccessDeniedHandler {
- @Override
- public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
- //当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
- response.setContentType("application/json;charset=UTF-8");
- response.setStatus(HttpStatus.OK.value());
- response.getWriter().write(JSONObject.toJSONString(ResponseResult.fail(HttpStatus.FORBIDDEN.value(),"鉴权失败")));
- response.getWriter().flush();
- }
- }
- /**
- * 全局捕获security的权限不足异常
- */
- @ExceptionHandler(value = AccessDeniedException.class)
- public void accessDeniedException(AccessDeniedException e) {
- log.error("权限不足异常!原因是:[{}]", e.getMessage());
- throw e;
- }
-
- /**
- * 全局捕获security的认证失败异常
- */
- @ExceptionHandler(value = AuthenticationException.class)
- public void authenticationException(AuthenticationException e) {
- log.error("用户认证失败异常!原因是:[{}]", e.getMessage());
- throw e;
- }
- @Resource
- private JwtAuthenticationEntryPoint authenticationEntryPoint;
-
- @Resource
- private JwtAccessDeniedHandler accessDeniedHandler;
- //配置异常处理器
- .exceptionHandling()
- //认证失败处理器
- .authenticationEntryPoint(authenticationEntryPoint)
- //鉴权失败处理器
- .accessDeniedHandler(accessDeniedHandler)
认证失败异常可使用错误的用户名密码登录进行测试
鉴权失败异常,暂时不能测试,需要补全权限代码后才可以进行测试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。