SpringSecurity 详解(通俗易懂)

SpringSecurity 详解

  • 1、SpringSecurity讲解
    • 1.1、SpringSecurity完整流程
    • 1.2、认证流程
  • 2、登录,退出,注册_分析说明
    • 2.1、登录
    • 2.2、校验
    • 2.3、退出
    • 2.4、注册
    • 2.5、SecurityContextHolder说明
  • 3、代码实现
    • 3.1、引入依赖
    • 3.2、登录 退出 注册
      • 3.2.1、SpringSecurity配置目录结构
      • 3.2.2、登录退出_controller + service
        • BlogLoginServiceImpl(service)
        • SecurityConfig——SpringSecurity配置类
        • UserDetailsServiceImpl(UserDetailsService接口实现类)
        • JwtAuthenticationTokenFilter 登录校验过滤器 (JWT认证过滤器)
        • AuthenticationEntryPoint ——认证成功处理器
        • AccessDeniedHandler ——认证失败处理器
      • 3.2.3、注册
        • UserServiceImpl
      • 3.2.4、工具类
        • BeanCopyUtils
        • JwtUtil
        • RedisCache
        • SecurityUtils
        • WebUtils
        • PathUtils

1、SpringSecurity讲解

1.1、SpringSecurity完整流程

在这里插入图片描述
在这里插入图片描述

  • Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
  • AuthenticationManager接口: Authentication()_认证方法
  • UserDetailsService接口: loadUserByUsername()_根据用户名查询用户信息
  • UserDetails接口:提供核心用户信息。通过UserDetailsService的 loadUserByUsername()拿到用户信息封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
  • UserDetailsService接口和UserDetails接口合二为一

1.2、认证流程

在这里插入图片描述

2、登录,退出,注册_分析说明

SpringSecurity安全认证 登陆 退出 注册
1.登录
1.1、第一次登陆时,根据userId生成jwt(能反解析出userId),返回给 浏览器并存储
1.2、用户下次再请求时,带上token, 登录校验过滤器会从token中解析出userId,
并通过这个key(userId) 在redis查询 key对应的 value(loginUser).
1.3、 如果找到,说明已经登录(将认证信息即用户信息 存入SecurityContextHolder)。 如果查不到,说明没有登录,需要重新登录
2、退出,从SecurityContextHolder中拿到认证信息loginUser,进而得到userId。 通过这个key(userId) 从redis中删除
3、 注册,使用BCryptPasswordEncoder密码加密

2.1、登录

①自定义登录接口  
  • 调用ProviderManager的方法进行认证 如果认证通过生成jwt
  • 把用户信息存入redis中

②自定义UserDetailsService

实现类UserDetailsServiceImpl implements UserDetailsService

  • 在实现类UserDetailsServiceImpl 中重写loadUserByUsername() 去数据库查询 用户名密码

  • 查到(将用户信息,权限信息 封装 返回类型UserDetails)

    注意配置passwordEncoder为BCryptPasswordEncoder

2.2、校验

①定义Jwt认证过滤器 (**登录认证过滤器**)
  • 获取token

  • 解析token获取其中的userid

  • 通过userid查redis,查到(从redis中获取用户信息),查不到 认证失败(重新登录)

  • 存入SecurityContextHolder(SpringSecurity的组件 存储 Authentication认证信息)

2.3、退出

  • 从SecurityContextHolder中拿到认证信息loginUser,进而得到userId 在redis 中删除
    这个key(userId)对应的键值对

2.4、注册

  • 密码加密(BCryptPasswordEncoder方式)
  • 数据库插入数据

2.5、SecurityContextHolder说明

  • SecurityContextHolder默认使用ThreadLocal 策略来存储 认证信息. 与线程绑定的策略
  • Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。
  • 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。

3、代码实现

3.1、引入依赖

    <!--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>

3.2、登录 退出 注册

3.2.1、SpringSecurity配置目录结构

在这里插入图片描述
在这里插入图片描述

3.2.2、登录退出_controller + service

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BlogLoginServiceImpl(service)

package com.sangeng.service.impl;
import com.sangeng.domain.ResponseResult;
import com.sangeng.domain.entity.LoginUser;
import com.sangeng.domain.entity.User;
import com.sangeng.domain.vo.BlogUserLoginVo;
import com.sangeng.domain.vo.UserInfoVo;
import com.sangeng.service.BlogLoginService;
import com.sangeng.utils.BeanCopyUtils;
import com.sangeng.utils.JwtUtil;
import com.sangeng.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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 java.util.Objects;@Service
public class BlogLoginServiceImpl implements BlogLoginService {@Autowiredprivate AuthenticationManager authenticationManager;//SecurityConfig类中配置AuthenticationManager注入到容器中,所有这里可以使用AuthenticationManager//接口AuthenticationManager  实现类ProviderManager  调用Authentication()进行认证@Autowiredprivate RedisCache redisCache; //封装好的工具类,使用redis, 本质:public RedisTemplate redisTemplate;@Overridepublic ResponseResult login(User user) {
//      接口authenticationManager.authenticate()进行认证 如果认证通过生成jwt    (ProviderManager是接口authenticationManager的实现类)
//      UsernamePasswordAuthenticationToken (父)-> AbstractAuthenticationToken -> Authentication(子)UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());//传用户名 密码Authentication authenticate = authenticationManager.authenticate(authenticationToken);//调用认证方法 参数是Authentication(实现类UsernamePasswordAuthenticationToken)
//        authenticationManager 实际上 会默认调用UserDetailsService接口去认证,所以要重写UserDetailsService接口//security会使用UserDetailsService实现类中的loadUserByUsername方法进行校验,所以要重写该方法
//        return new LoginUser(user) 返回给 Authentication authenticate,包含了 UserDetails对象(权限信息 + 用户信息)//        authenticationManager.authenticate() 认证的时候,自动进行密码比对//判断是否通过if (Objects.isNull(authenticate)){throw new RuntimeException("用户名或密码错误");}//获取userid,生成token           Authentication对象:(username,password) ->认证(数据库查找用户名密码+用户信息+权限信息) UserDetailsService.loadUserByUsername()实现LoginUser loginUser = (LoginUser) authenticate.getPrincipal();//获取认证主体 强转成LoginUserString userId = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userId); //jwt:把userid加密后的密文 即token    可以解析token拿到userid//把用户信息(用户+权限)存入redis   格式(bloglogin:id,loginUser)  (包含用户信息+权限信息)redisCache.setCacheObject("bloglogin:"+userId,loginUser);/**  redisCache.setCacheObject()方法 是工具类封装好的方法,等同于如下代码@Autowiredpublic RedisTemplate redisTemplate;public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}*///把token和userinfo封装 返回//把User转换成UserInfoVOUserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);BlogUserLoginVo vo = new BlogUserLoginVo(jwt,userInfoVo);   //{ token ,userInfo}// 响应:登录成功后,返回给浏览器一个token,下次再请求的时候,需要带上这个token即可,即可识别出哪个具体的用户
//        为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把userId作为key,即 (userId,loginUser)/** SpringSecurity安全认证    登陆 退出* 第一次登陆时,根据userId生成jwt(能反解析出userId),返回给 浏览器并存储*  用户下次再请求时,带上token, 登录校验过滤器会从token中解析出userId,*  并通过这个key(userId) 在redis查询 key对应的 value(loginUser).*  如果找到,说明已经登录(将认证信息即用户信息 存入SecurityContextHolder)。 如果查不到,说明没有登录,需要重新登录*  退出时,从SecurityContextHolder中拿到认证信息loginUser,进而得到userId。 通过这个key(userId) 从redis中删除*/return ResponseResult.okResult(vo);}@Overridepublic ResponseResult logout() {//获取token 解析tokenAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();//从SecurityContextHolder中 拿到Authentication对象//SecurityContextHolder默认使用ThreadLocal 策略来存储 认证信息. 与线程绑定的策略// Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。//当用户再次发送请求时,携带token,则自动把token存入到 SecurityContextHolderLoginUser loginUser = (LoginUser) authentication.getPrincipal(); //获取认证主体//获取useridLong userId = loginUser.getUser().getId();// 删除redis中的用户信息redisCache.deleteObject("bloglogin:"+userId);//        redisTemplate.delete(key);return ResponseResult.okResult();}
}

SecurityConfig——SpringSecurity配置类

package com.sangeng.config;import com.sangeng.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//    为啥注入的不是实现类呢? 不传实现类是为了符合开闭原则@AutowiredAuthenticationEntryPoint authenticationEntryPoint; //认证成功处理器@AutowiredAccessDeniedHandler accessDeniedHandler; //认证失败处理器@Beanpublic PasswordEncoder passwordEncoder(){ //对比密码时 加密方式  要改成BCryptPasswordEncoder()return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/login").anonymous()   //接口必须携带token.antMatchers("/logout").authenticated()//退出接口 必须认证,即必须携带token才能 发出退出请求.antMatchers("/user/userInfo").authenticated() //这个接口需要认证之后才能访问
//                .antMatchers("/upload").authenticated() //前端vue上传图片时,没有要求token,所以后端不需要认证,不需要传token即可
//                .antMatchers("/link/getAllLink").authenticated() //这个接口需要认证之后才能访问// 除上面外的所有请求全部不需要认证即可访问.anyRequest().permitAll();//配置异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) //认证失败处理器.accessDeniedHandler(accessDeniedHandler); //授权失败处理器http.logout().disable(); //关闭默认 logout功能//将过滤器 配置到 UsernamePasswordAuthenticationFilter之前http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//允许跨域http.cors();}@Override@Bean     //暴露ProvideManager方法注入到spring bean容器中public AuthenticationManager authenticationManagerBean() throws Exception {
//        这一段配置用于登录时认证,只有使用了这个配置才能自动注入AuthenticationManager,并使用它来进行用户认证
//        使用的时候,直接注入此bean对象即可使用  @Autowired  private AuthenticationManager authenticationManager;return super.authenticationManagerBean();}
}

UserDetailsServiceImpl(UserDetailsService接口实现类)

package com.sangeng.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sangeng.constants.SystemConstants;
import com.sangeng.domain.entity.LoginUser;
import com.sangeng.domain.entity.User;
import com.sangeng.mapper.MenuMapper;
import com.sangeng.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.List;
import java.util.Objects;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate MenuMapper menuMapper;/*** 通过用户名 查找用户* @param username* @return UserDetails 对象*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//security会使用UserDetailsService实现类中的loadUserByUsername方法进行校验//根据用户名查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserName,username);User user = userMapper.selectOne(queryWrapper);//判断是否查到用户  如果没查到抛出异常if (Objects.isNull(user)){ throw new RuntimeException("用户不存在"); }//返回用户信息    返回的是UserDatails对象,后面才做密码校验   密码SpringSecurity自动校验//TODO 查询权限信息封装 如果是后台用户才需要查询权限封装 (前台用户不需要查询权限)if (user.getType().equals(SystemConstants.ADMIN)){ //如果是管理员,返回 用户信息+权限信息List<String> perms = menuMapper.selectPermsByUserId(user.getId()); //权限列表return new LoginUser(user,perms);}//  定义一个loginUser 实现 UserDetails,即可返回return new LoginUser(user,null); //UserDetails对象(权限信息 + 用户信息)}
}

JwtAuthenticationTokenFilter 登录校验过滤器 (JWT认证过滤器)

package com.sangeng.filter;import com.alibaba.fastjson.JSON;
import com.sangeng.domain.ResponseResult;
import com.sangeng.domain.entity.LoginUser;
import com.sangeng.enums.AppHttpCodeEnum;
import com.sangeng.utils.JwtUtil;
import com.sangeng.utils.RedisCache;
import com.sangeng.utils.WebUtils;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
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.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
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//      登录校验过滤器        定义Jwt认证过滤器  浏览器登录后,再次发送请求时,会携带token,让服务器验证//获取请求头中的tokenString token = request.getHeader("token");if(!StringUtils.hasText(token)){ //没有token就说明是第一次登录,直接放行//说明该接口 不需要登录,直接放行filterChain.doFilter(request,response);return;   //放行,程序到此结束}//解析token 获取useridClaims claims = null;try {claims = JwtUtil.parseJWT(token); //解析token 得到userId} catch (Exception e) {e.printStackTrace();//token超时,token非法//响应告诉前端重新登录 (响应一个json格式)ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);// 401 请重新登录WebUtils.renderString(response, JSON.toJSONString(result)); //转成json,并把json串写到响应体当中return;}String userId = claims.getSubject(); // 拿到userid//从redis获取用户信息(如果获取失败,则验证失败)LoginUser loginUser = redisCache.getCacheObject("bloglogin:" + userId);//如果获取不到if (Objects.isNull(loginUser)){//说明登录过期,重新登录ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);// 401 请重新登录WebUtils.renderString(response, JSON.toJSONString(result)); //转成json,并把json串写到响应体当中return;}//登录成功,将用户信息loginUser  存入SecurityContextHolderUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);//未认证,用两个参数,认证过,用三个参数SecurityContextHolder.getContext().setAuthentication(authenticationToken);//        都执行完了要 放行,让下面的filter处理filterChain.doFilter(request,response);}
}

AuthenticationEntryPoint ——认证成功处理器

package com.sangeng.handler.security;import com.alibaba.fastjson.JSON;
import com.sangeng.domain.ResponseResult;
import com.sangeng.enums.AppHttpCodeEnum;
import com.sangeng.utils.WebUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
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 {//    AuthenticationEntryPoint 认证失败处理器@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {authException.printStackTrace(); //打印异常信息//        BadCredentialsException
//        InsufficientAuthenticationException//认证失败 会 抛出BadCredentialsException异常ResponseResult result = null;if (authException instanceof BadCredentialsException) {result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(), authException.getMessage());} else if (authException instanceof InsufficientAuthenticationException) {result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);}else {result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR,"认证或授权失败");//给出错误的提示信息}//转化为json响应给前端 (认证失败后,响应给前端 自定义的 json格式字符串)WebUtils.renderString(response, JSON.toJSONString(result));}
}

AccessDeniedHandler ——认证失败处理器

package com.sangeng.handler.security;import com.alibaba.fastjson.JSON;
import com.sangeng.domain.ResponseResult;
import com.sangeng.enums.AppHttpCodeEnum;
import com.sangeng.utils.WebUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.AuthenticationEntryPoint;
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 {//    AccessDeniedHandler 授权失败处理器@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {accessDeniedException.printStackTrace();ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);//响应给前端WebUtils.renderString(response, JSON.toJSONString(result));}
}

3.2.3、注册

在这里插入图片描述

UserServiceImpl

package com.sangeng.service.impl;import ...
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate PasswordEncoder passwordEncoder; //SecurityConfig中配置类中将PasswordEncoder(对密码加密处理)注入到容器中,这里才能使用@Overridepublic ResponseResult register(User user) {//对数据进行非空判断   ("" 或者 null)if (!StringUtils.hasText(user.getUserName())){throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);}if (!StringUtils.hasText(user.getPassword())){throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);}if (!StringUtils.hasText(user.getEmail())){throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);}if (!StringUtils.hasText(user.getNickName())){throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);}//对数据进行 是否存在判断 (用户名,邮箱是否已存在)if (userNameExist(user.getUserName())){throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);}if (emailExist(user.getEmail())){throw new SystemException(AppHttpCodeEnum.EMAIL_EXIST);}//        配置类SecurityConfig中 设置加密方式 (不使用默认加密方式)
//        @Bean
//        public PasswordEncoder passwordEncoder(){ //对比密码时 加密方式  要改成BCryptPasswordEncoder()
//            return new BCryptPasswordEncoder();
//        }//对密码进行加密String encodePassword = passwordEncoder.encode(user.getPassword());//对明文密码 进行加密,得到密文user.setPassword(encodePassword); //将password加密后的密文,存到user中//存入数据库中save(user);  // IService中的方法 mybatisplus提供return ResponseResult.okResult();}

3.2.4、工具类

BeanCopyUtils

package com.sangeng.utils;
import org.springframework.beans.BeanUtils;
import java.util.List;
import java.util.stream.Collectors;public class BeanCopyUtils {//    构造方法设置为私有的方法private BeanCopyUtils() {}
//    单个实体类拷贝(将一个源对象 拷贝 至  字节码class)
//    通过反射创建目标对象,然后再拷贝public static <V> V copyBean(Object source,Class<V> clazz) {//创建目标对象     传过来什么类型,就返回什么类型,使用泛型(  <V> V泛型方法,返回值类型 )V result = null;  //提升作用域try {result = clazz.newInstance();//实现属性copyBeanUtils.copyProperties(source, result);} catch (Exception e) {throw new RuntimeException(e);}//返回结果return result;}//    集合拷贝
//  (后面要用)声明泛型O,V  返回类型     public <T> void say(){} 表明是泛型方法public static <O,V>  List<V> copyBeanList(List<O> list, Class<V> clazz){
//        先将list集合  转成流->流当中元素的转换(转换方式copyBean方法) 返回一个泛型V -> 收集操作,泛型转成listreturn list.stream().map(o -> copyBean(o, clazz)).collect(Collectors.toList());}//    测试使用方法
//    public static void main(String[] args) {
//        Article article = new Article();
//        article.setId(1L);
//        article.setTitle("hello");
//
//        HotArticleVo hotArticleVo = copyBean(article, HotArticleVo.class);
//        System.out.println(hotArticleVo);
//    }}

JwtUtil

package com.sangeng.utils;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 = 24*60 * 60 *1000L;// 60 * 60 *1000  一个小时//设置秘钥明文public static final String JWT_KEY = "sangeng";public static String getUUID(){String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成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("sg")     // 签发者.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 {String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";Claims claims = parseJWT(token);System.out.println(claims);}/*** 生成加密后的秘钥 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();}}

RedisCache

package com.sangeng.utils;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
{@Autowiredpublic 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);}/*** redis 是一个键值对的NoSQL数据库 结构={  key,value=hash{hkey,value}  }* @param key* @param hkey* @param v*/public void incrementCacheMapValue(String key , String hkey, int v){
//        v 表示递增值   浏览量每次增加几redisTemplate.opsForHash().increment(key,hkey,v); //遍历Map, 找到 key对应的 hash(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);}
}

SecurityUtils

package com.sangeng.utils;import com.sangeng.domain.entity.LoginUser;import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;/*** @Author 三更  B站: https://space.bilibili.com/663528522*/
public class SecurityUtils
{/*** 获取用户**/public static LoginUser getLoginUser(){return (LoginUser) getAuthentication().getPrincipal();}/*** 获取Authentication*/public static Authentication getAuthentication() {return SecurityContextHolder.getContext().getAuthentication();}public static Boolean isAdmin(){Long id = getLoginUser().getUser().getId();return id != null && id.equals(1L);}public static Long getUserId() {return getLoginUser().getUser().getId();}
}

WebUtils

package com.sangeng.utils;import org.springframework.web.context.request.RequestContextHolder;import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;public class WebUtils
{/*** 将字符串渲染到客户端* @param response 渲染对象* @param string 待渲染的字符串* @return null*/public static void 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();}}public static void setDownLoadHeader(String filename, HttpServletResponse response) throws UnsupportedEncodingException {response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fname= URLEncoder.encode(filename,"UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition","attachment; filename="+fname);}
}

PathUtils

package com.sangeng.utils;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;/*** @Author 三更  B站: https://space.bilibili.com/663528522*/
public class PathUtils {public static String generateFilePath(String fileName){ //fileName为原始文件名 111.png//根据日期生成路径   2022/1/15/SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");//日期格式化String datePath = sdf.format(new Date());//uuid作为文件名  生成uuidString uuid = UUID.randomUUID().toString().replaceAll("-", "");//后缀和文件后缀一致int index = fileName.lastIndexOf("."); //找到111.png中.的索引// test.jpg -> .jpgString fileType = fileName.substring(index); //截取index后面的字符串 [index, ... )
//        拼接  2022/1/15/+uuid+.jpg 即可得到文件路径return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1380494.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

一个在线的PS

http://www.uupoop.com/

代码实现ps切换工具

在下最近在研究制作ps的扩展插件&#xff0c;由于工作原因&#xff0c;设计使用ps较多&#xff0c;但是手动处理倒角还是比较麻烦&#xff0c;所以想开发了一款倒角用的小扩展插件&#xff0c;减少一下工作量&#xff0c;到网上找了些有关ps插件制作的教程&#xff0c;先实现一…

unity实现角色体力功能【体力条+体力计算】

导读&#xff1a;实现功能 1、角色体力计算 2、角色疲劳动画 3、体力条制作、跟随 默认做好角色的idle/run/walk动画、切换和玩家输入&#xff0c;我使用的是新输入系统&#xff0c;动画时单变量混合树&#xff0c;参数Sports。 【每一部分功能根据自己需求观看哦】 1、角色体…

Mysql:Access denied for user ‘root‘@‘localhost‘ (using password:YES)解决方案

最近在配置Maven以及Mybatis时&#xff0c;连接localhost数据库时出现无法连接&#xff0c;用cmd测试时报错&#xff1a;Access denied for user ‘ODBC’‘localhost’ (using password: NO)&#xff0c;这个意思就是不允许远程访问&#xff0c;一开始笔者进入mysql试了一下是…

【C# Programming】C#第一课(自己学习的笔记)

目录 一、C# 介绍 1.1 托管代码(Manage Code ) &#xff1a; 1.2 基础类型库 (Base Class Library)&#xff1a; 1.3 程序集(Assembly)&#xff1a; 1.4 .NET 框架&#xff1a; 1.5 公共中间语言(Common Intermediate Language)&#xff0c;简称 IL。 1.6 C#编译器将源代…

Ceph集群安装部署

Ceph集群安装部署 目录 Ceph集群安装部署 1、环境准备 1.1 环境简介1.2 配置hosts解析(所有节点)1.3 配置时间同步2、安装docker(所有节点)3、配置镜像 3.1 下载ceph镜像(所有节点执行)3.2 搭建制作本地仓库(ceph-01节点执行)3.3 配置私有仓库(所有节点执行)3.4 为 Docker 镜像…

Hazel 引擎学习笔记

目录 Hazel 引擎学习笔记学习方法思考引擎结构创建工程程序入口点日志系统Premake\MD没有 cpp 文件的项目会出错include 到某个库就要包含这个库的路径&#xff0c;注意头文件展开 事件系统 获取和利用派生类信息预编译头文件抽象窗口类和 GLFWgit submodule addpremake 脚本禁…

自定义element-plus的弹框样式

项目中弹框使用频繁,需要统一样式风格,此组件可以自定义弹框的头部样式和内容 一、文件结构如下: 二、自定义myDialog组件 需求&#xff1a; 1.自定义弹框头部背景样式和文字 2.自定义弹框内容 3.基本业务流程框架 components/myDialog/index.vue完整代码&#xff1a; &…

(本人亲测有效)华为magicbook 16SE笔记本电脑重装系统过程

目录 1&#xff1a;先看看我重新装了系统以及格式化D盘效果&#xff0c;以及我把D盘分为E,F盘后的效果&#xff0c; 2&#xff1a;过程 2.1先说明为什么我重装系统 2.2 重装系统过程 3&#xff1a; D盘分为E,F盘过程 3.1以下为具体格式化D盘&#xff0c;数据请备份&#x…

6-Ngnix配置反向代理

1.前提 虚拟机能连接外网 仿真http应用需在本虚拟机启用(原因&#xff1a;只有一台虚拟机做测试) http_8080和http_8081要启用&#xff08;http测试应用&#xff09; [rootcent79-2 ~]# ls -l http_* -rwxr-xr-x 1 root root 6391676 Jul 19 13:39 http_8080 -rwxr-xr-x 1 …

PAT1044 Shopping in Mars

个人学习记录&#xff0c;代码难免不尽人意。 做了这么多题难得本题不看答案一遍过&#xff0c;很是激动。 Shopping in Mars is quite a different experience. The Mars people pay by chained diamonds. Each diamond has a value (in Mars dollars M$). When making the pa…

【数据结构】反转链表、链表的中间节点、链表的回文结构(单链表OJ题)

正如标题所说&#xff0c;本文会图文详细解析三道单链表OJ题&#xff0c;分别为&#xff1a; 反转链表 &#xff08;简单&#xff09; 链表的中间节点 &#xff08;简单&#xff09; 链表的回文结构 &#xff08;较难&#xff09; 把他们放在一起讲的原因是&#xff1a; 反转链…

QGIS二次开发六:VS不借助QT插件创建UI界面

上一篇博客我们说了在VS中如何使用QT插件来创建UI界面&#xff0c;但是我们二次开发QGIS的第一篇博客就说了&#xff0c;最好使用OSGeo4W中自动下载的QT进行QGIS二次开发&#xff0c;这样兼容性是最好的&#xff0c;那么该如何在VS中不使用外部安装的QT以及QT的VS插件情况下进行…

提取有像素的掩码和原图

有些数据集给的掩码是全黑图片&#xff0c;需要将全黑的掩码剔除&#xff0c;保留有标签的掩码。 DDR-dataset 眼底图像处理 from PIL import Image import cv2 import osdef extract_mask_and_original(mask_path, original_path, output_folder):# 读取黑白掩码图片和同名原…

【Java】智慧工地云平台源码-支持私有化部署+硬件设备

智慧工地硬件设备包括&#xff1a;AI识别一体机、智能广播音响、标养箱、塔机黑匣子、升降机黑匣子、吊钩追踪控制设备、扬尘监测设备、喷淋设备。 1.什么是AI危险源识别 AI危险源识别是指基于智能视频分析技术&#xff0c;对视频图像信息进行自动分析识别&#xff0c;以实时监…

photoshop指定打开psd文件方式

1、打开考生文件夹下的psd文件 2、右击这个psd——打开——或打开方式&#xff08;选其他默认程序&#xff09; 3、路径一般在 C:\Program Files\Adobe 或者是C:\Program Files&#xff08;x86&#xff09;\Adobe 下&#xff0c;打开后找到Photoshop.exe后——打开 4、点击勾选…

C++文件类(整理自C语言中文网-全)

C文件类&#xff08;文件流类&#xff09;及用法详解 《C输入输出流》一章中讲过&#xff0c;重定向后的 cin 和 cout 可分别用于读取文件中的数据和向文件中写入数据。除此之外&#xff0c;C 标准库中还专门提供了 3 个类用于实现文件操作&#xff0c;它们统称为文件流类&…

C语言案例 分数列求和-11

题目&#xff1a;有一分数列&#xff1a;2 / 1,3 / 2,5 / 3,8 / 5,13 / 8,21 / 13 …求出这个数列的前20项之和。 程序分析 这是一个典型的分数列数学逻辑题&#xff0c;考究这类题目是需要从已知的条件中找到它们的分布规律 我们把前6荐的分子与分母分别排列出来&#xff0c;…

快速使用公网远程访问内网群晖NAS 7.X版 【内网穿透】

公网远程访问内网群晖NAS 7.X版 【内网穿透】 文章目录 公网远程访问内网群晖NAS 7.X版 【内网穿透】前言1. 在群晖控制面板找到“终端机和SNMP”2. 建立一条连接公网数据隧道3. 获取公网访问内网群晖NAS的数据隧道入口 前言 群晖NAS作为应用较为广泛的小型数据存储中心&#…

ⅰsee是什么意思_see是什么意思

展开全部 v. 看见&#xff0c;明白&#xff0c;e68a84e8a2ad3231313335323631343130323136353331333433626539了解&#xff0c;经历&#xff0c;设想 n. 主教教区&#xff0c;主角权限 用法&#xff1b; 1.see的基本意思是指一般视觉意义上的“看见”,也可指有意识地“观察”,引…