配置示例
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
创建实体类
src/main/java/com/mahe666/entity/User.java
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 用户表(User)实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = -40356785423868312L;
/**
* 主键
*/
@TableId
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 密码
*/
private String password;
/**
* 账号状态(0正常 1停用)
*/
private String status;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phonenumber;
/**
* 用户性别(0男,1女,2未知)
*/
private String sex;
/**
* 头像
*/
private String avatar;
/**
* 用户类型(0管理员,1普通用户)
*/
private String userType;
/**
* 创建人的用户id
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer delFlag;
}
src/main/java/com/mahe666/entity/Menu.java
/**
* 菜单表(Menu)实体类
*/
@TableName(value="menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {
private static final long serialVersionUID = -54979041104113736L;
@TableId
private Long id;
/**
* 菜单名
*/
private String menuName;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 菜单状态(0显示 1隐藏)
*/
private String visible;
/**
* 菜单状态(0正常 1停用)
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
/**
* 是否删除(0未删除 1已删除)
*/
private Integer delFlag;
/**
* 备注
*/
private String remark;
数据源
这里需要配置数据源,然后封装UserMapper,MenuMapper
统一返回响应类
src/main/java/com/mahe666/domain/ResponseResult.java
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息,如果有错误时,前端可以获取该字段进行提示
*/
private String msg;
/**
* 查询到的结果数据,
*/
private T data;
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
Redis配置类
src/main/java/com/mahe666/config/RedisConfig.java
import com.mahe666.util.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
Redis使用FastJson序列化
src/main/java/com/mahe666/util/FastJsonRedisSerializer.java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}
JWT工具类
src/main/java/com/mahe666/util/JwtUtil.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "mahe666";
public static String getUUID(){
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("mahe666") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
// 注意! 这里需要测试用于加密的秘钥是否可用,秘钥可能会导致编码失败
// 加密,创建JWT,这个工具类中有三个重载方法 createJWT
String jwt = createJWT("1234567");
// 解密
System.out.println(parseJWT(jwt).getSubject());
// Claims claims = parseJWT(jwt);
// System.out.println(claims.getSubject());
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
对RedisTemplate进一步封装
src/main/java/com/mahe666/util/RedisCache.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
设置响应信息
src/main/java/com/mahe666/util/WebUtils.java
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WebUtils
{
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}
手动实现 UserDetailsService 接口
src/main/java/com/mahe666/service/impl/UserDetailsServiceImpl.java
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
// 自己的UserDetails接口实现类
import com.mahe666.domain.UserDetailsImpl;
// 自己的User实体类
import com.mahe666.entity.User;
import com.mahe666.mapper.MenuMapper;
import com.mahe666.mapper.UserMapper;
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.List;
import java.util.Objects;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Resource
private MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
if (Objects.isNull(user)){
// 之前说到SpringSecurity有一个异常处理器,所以这里直接抛出就好
// 如果没有,我们也可以手写一个全局的异常处理器
throw new RuntimeException("用户名或者密码错误");
}
// List<String> list = new ArrayList<>(Arrays.asList("test","hello"));
List<String> permissionList = menuMapper.selectPermsByUserId(user.getId());
//返回自己的UserDetails实现类
return new UserDetailsImpl(user,permissionList);
}
}
手动实现 UserDetails 接口
src/main/java/com/mahe666/domain/UserDetailsImpl.java
import com.alibaba.fastjson.annotation.JSONField;
// 自己的实体类
import com.mahe666.entity.User;
import lombok.AllArgsConstructor;
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;
import java.util.stream.Collectors;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailsImpl implements UserDetails {
private User user;
// 查询到的权限
private List<String> permissions;
// 手写一个只有 user 和 permissions 为参数的构造方法
public UserDetailsImpl(User user,List<String> permissions){
this.user = user;
this.permissions = permissions;
}
// 经过处理的权限
// 注意!这个泛型是由框架提供的,所以,Redis为了安全考虑,默认不会去序列化这个成员变量,所以如果不知道的情况下去调用会出问题(直接报异常)
// 所以,我们这里只传 permissions 进去就可以
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
// 用来返回权限信息的方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 如果不为空,则代表里面有值,直接返回就好
if (authorities != null){
return authorities;
}else{
// 将 permissions 中的元素封装成 SimpleGrantedAuthority 对象(GrantedAuthority 的子类)
// 下面是一般方法
// List<GrantedAuthority> authorities = new ArrayList<>();
// for (String permission : permissions) {
// SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
// authorities.add(authority);
// }
// 这是函数式编程
authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
}
// 获取密码
@Override
public String getPassword() {
return user.getPassword();
}
// 获取用户名
@Override
public String getUsername() {
return user.getUserName();
}
/*
下面的四个布尔类型的方法先全都改为true
*/
// 判断是否没有过期
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
// 是否没有超时
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
登录登出
src/main/java/com/mahe666/controller/LoginController.java
import com.mahe666.domain.ResponseResult;
import com.mahe666.entity.User;
import com.mahe666.service.LoginService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class LoginController {
@Resource
private LoginService loginService;
@PostMapping("/login")
// 返回的是统一响应类
// 前端传过来的如果是Json,则我们需要从请求体中获取参数
public ResponseResult login(@RequestBody User user){
// 登录
return loginService.login(user);
}
@RequestMapping("/logout")
public ResponseResult logout(){
return loginService.logout();
}
}
src/main/java/com/mahe666/service/LoginService.java
import com.mahe666.domain.ResponseResult;
import com.mahe666.entity.User;
public interface LoginService {
ResponseResult login(User user);
ResponseResult logout();
}
src/main/java/com/mahe666/service/impl/LoginServiceImpl.java
import com.mahe666.domain.ResponseResult;
import com.mahe666.domain.UserDetailsImpl;
import com.mahe666.entity.User;
import com.mahe666.service.LoginService;
import com.mahe666.util.JwtUtil;
import com.mahe666.util.RedisCache;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Service
public class LoginServiceImpl implements LoginService {
// 注入 AuthenticationManager
@Resource
private AuthenticationManager authenticationManager;
// 注入自己编写的 RedisTemplate 扩展
@Resource
private RedisCache redisCache;
@Override
public ResponseResult login(User user) {
// 通过AuthenticationManager的authenticate方法进行用户认证
// 注意!这个方法的参数是 Authentication 类型的,所以我们需要找到这个 Authentication 接口的实现类,这里我们就选择
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
// 注意这里有一个返回值,我们只需要判断它是否为空就可以看出是否通过认证
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 如果认证没通过,给出对应的提示
if (Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
// 如果认证通过,使用userid生成Jwt,然后通过 ResponseResult 统一返回
// 我们通过断点查看到了返回值是 UserDetailsImpl 类型的,所以这里直接强转
UserDetailsImpl principal = (UserDetailsImpl) authenticate.getPrincipal();
// 获取userid
String userid = principal.getUser().getId().toString();
// 创建jwt(token)
String token = JwtUtil.createJWT(userid);
//准备返回值token
Map<String,String> map = new HashMap<>();
map.put("token",token);
// 把完整的用户信息传入Redis,userid作为Key
redisCache.setCacheObject("login:"+userid,principal);
return new ResponseResult(200,"登陆成功",map);
}
@Override
public ResponseResult logout() {
//获取 SecurityContextHolder 中的userid
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl principal = (UserDetailsImpl) authentication.getPrincipal();
Long userid = principal.getUser().getId();
//删除Redis中的值
redisCache.deleteObject("login:"+userid);
return new ResponseResult(200,"注销成功");
}
}
认证过滤器
src/main/java/com/mahe666/filter/JwtAuthenticationTokenFilter.java
import com.mahe666.domain.UserDetailsImpl;
import com.mahe666.util.JwtUtil;
import com.mahe666.util.RedisCache;
import io.jsonwebtoken.Claims;
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 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;
@Component
// 这里不像javaweb中去实现filter接口,filter接口有问题,我们这里选择的是框架提供的filter接口的实现类,确保每次请求只会被过滤一次
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
// 注入 redistemplate 的手写扩展
@Resource
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String token = request.getHeader("token");
// 如果token这个字符串里面没有内容的话
if (!StringUtils.hasText(token)){
//直接放行
filterChain.doFilter(request,response);
// 直接堵住,否则响应回来的时候还会执行下面的代码
return;
}
// 解析token
String userid;
try {
// 解析
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
// 如果解析失败
throw new RuntimeException("token非法");
}
// 从Redis中获取用户信息
String redisKey = "login:"+userid;
// 去Redis中拿数据,getCacheObject方法,用了方法泛型
UserDetailsImpl user = redisCache.getCacheObject(redisKey);
if (Objects.isNull(user)){
throw new RuntimeException("用户未登录");
}
// 传入用户的权限
// 存入SecurityContextHolder中
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 放行
filterChain.doFilter(request,response);
}
}
跨域配置类
src/main/java/com/mahe666/config/CorsConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
处理器
认证异常处理器
src/main/java/com/mahe666/handler/AuthenticationEntryPointImpl.java
import com.alibaba.fastjson.JSON;
import com.mahe666.domain.ResponseResult;
import com.mahe666.util.WebUtils;
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.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
// 这个方法有三个对象,前面两个不多说,最后一个就是 认证异常对象
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 认证失败处理
ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), authException.getMessage());
// 转换为Json字符串
String json = JSON.toJSONString(result);
// 处理异常
WebUtils.renderString(response,json);
}
}
授权异常处理器
src/main/java/com/mahe666/handler/AccessDeniedHandlerImpl.java
import com.alibaba.fastjson.JSON;
import com.mahe666.domain.ResponseResult;
import com.mahe666.util.WebUtils;
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.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
// 这里的第三个参数是 授权异常对象
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 授权失败处理
ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "您没有权限访问");
// 转换为Json字符串
String json = JSON.toJSONString(result);
// 处理异常
WebUtils.renderString(response,json);
}
}
认证成功处理器
src/main/java/com/mahe666/handler/AuthenticationSuccessHandlerImpl.java
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
// 这里我们只需要实现接口里面的抽象方法就好
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("认证成功");
}
}
认证失败处理器
src/main/java/com/mahe666/handler/AuthenticationFailureHandlerImpl.java
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
// 接口中只有这一个方法
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("认证失败了");
}
}
登出成功处理器
src/main/java/com/mahe666/handler/LogoutSuccessHandlerImpl.java
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("登出成功");
}
}
SpringSecurity配置类
src/main/java/com/mahe666/config/SecurityConfig.java
import com.mahe666.filter.JwtAuthenticationTokenFilter;
import com.mahe666.handler.AccessDeniedHandlerImpl;
import com.mahe666.handler.AuthenticationEntryPointImpl;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 注入 BCryptPasswordEncoder 替换掉默认的PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 注入 AuthenticationManager
// 当前类的父类 WebSecurityConfigurerAdapter 的这个方法的注释也说明了可以通过重写该方法以暴露 AuthenticationManager
// 最后,不要忘记有个Bean
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 注入我们写的token验证过滤器
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// 注入认证异常处理器
@Resource
private AuthenticationEntryPointImpl authenticationEntryPointImpl;
// 注入授权异常处理器
@Resource
private AccessDeniedHandlerImpl accessDeniedHandlerImpl;
// 重写父类configure方法,暴露登录接口,使其不需token验证就能够访问
// 注意:重写的是参数为HttpSecurity的方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不会创建HttpSession,也不会通过HttpSession获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问,匿名访问的意思就是只有未登录的状态才可以访问
// 如果是.permitAll(),则是都可以访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 配置异常处理器
http.exceptionHandling()
// 配置认证异常处理器
.authenticationEntryPoint(authenticationEntryPointImpl)
// 配置授权异常处理器
.accessDeniedHandler(accessDeniedHandlerImpl);
// 允许跨域
http.cors();
}
}