Detailed explanation of SpringSecurity (easy to understand)

1. SpringSecurity explanation

1.1. SpringSecurity complete process

Insert image description here
Insert image description here

  • Authentication interface: Its implementation class represents the user currently accessing the system and encapsulates user-related information.
  • AuthenticationManager interface: Authentication()_authentication method
  • UserDetailsService interface: loadUserByUsername()_Query user information based on user name
  • UserDetails interface: Provides core user information. Get the user information through loadUserByUsername() of UserDetailsService and encapsulate it into a UserDetails object and return it. This information is then encapsulated into the Authentication object.
  • The UserDetailsService interface and the UserDetails interface are combined into one

1.2. Certification process

Insert image description here

2. Log in, log out, register_Analysis instructions

SpringSecurity security authentication login and exit registration
1. Login
1.1. When logging in for the first time, jwt is generated based on the userId (can reversely parse the userId), returned to the browser and stored
1.2. The next time the user requests, bring the token and log in to the school. The authentication filter will parse the userId from the token,
and query the value (loginUser) corresponding to the key in redis through this key (userId).
1.3. If found, it means that you have logged in (the authentication information, that is, the user information, is stored in the SecurityContextHolder). If it cannot be found, it means you are not logged in and you need to log in again.
2. Exit, get the authentication information loginUser from the SecurityContextHolder, and then get the userId. Delete from redis through this key (userId)
3. Register and use BCryptPasswordEncoder password encryption

2.1. Login

①自定义登录接口  
  • Call the ProviderManager method for authentication. If the authentication passes, jwt is generated.
  • Store user information in redis

②Customize UserDetailsService

Implement class UserDetailsServiceImpl implements UserDetailsService

  • Rewrite loadUserByUsername() in the implementation class UserDetailsServiceImpl to query the username and password in the database

  • Found (encapsulate user information and permission information and return type UserDetails)

    Pay attention to configuring passwordEncoder as BCryptPasswordEncoder

2.2. Verification

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

  • Parse the token to get the userid in it

  • Check redis through userid and find (obtain user information from redis). Authentication failure cannot be found (log in again)

  • Store in SecurityContextHolder (SpringSecurity component stores Authentication authentication information)

2.3. Exit


  • Get the authentication information loginUser from the SecurityContextHolder, and then get the userId. Delete the key-value pair corresponding to this key (userId) in redis

2.4. Registration

  • Password encryption (BCryptPasswordEncoder method)
  • Database insert data

2.5. SecurityContextHolder description

  • SecurityContextHolder uses the ThreadLocal strategy by default to store authentication information. Thread-bound strategies
  • Spring Security automatically binds authentication information to the current thread when the user logs in, and automatically clears the authentication information of the current thread when the user logs out.
  • In the interface, we use the authenticate method of AuthenticationManager to perform user authentication, so we need to configure the AuthenticationManager to be injected into the container in SecurityConfig.

3. Code implementation

3.1. Introducing dependencies

    <!--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. Log in and log out

3.2.1. SpringSecurity configuration directory structure

Insert image description here
Insert image description here

3.2.2. Log in and log out_controller + service

Insert image description here
Insert image description here
Insert image description here

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 {
    
    

    @Autowired
    private AuthenticationManager authenticationManager;
    //SecurityConfig类中配置AuthenticationManager注入到容器中,所有这里可以使用AuthenticationManager
    //接口AuthenticationManager  实现类ProviderManager  调用Authentication()进行认证

    @Autowired
    private RedisCache redisCache; //封装好的工具类,使用redis, 本质:public RedisTemplate redisTemplate;

    @Override
    public 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();//获取认证主体 强转成LoginUser
        String 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()方法 是工具类封装好的方法,等同于如下代码

        @Autowired
        public RedisTemplate redisTemplate;

        public <T> void setCacheObject(final String key, final T value){
            redisTemplate.opsForValue().set(key, value);
        }
         */

        //把token和userinfo封装 返回
        //把User转换成UserInfoVO
        UserInfoVo 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);
    }

    @Override
    public ResponseResult logout() {
    
    

        //获取token 解析token
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();//从SecurityContextHolder中 拿到Authentication对象
        //SecurityContextHolder默认使用ThreadLocal 策略来存储 认证信息. 与线程绑定的策略
        // Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。
        //当用户再次发送请求时,携带token,则自动把token存入到 SecurityContextHolder
        LoginUser loginUser = (LoginUser) authentication.getPrincipal(); //获取认证主体
        //获取userid
        Long userId = loginUser.getUser().getId();
        // 删除redis中的用户信息
        redisCache.deleteObject("bloglogin:"+userId);

//        redisTemplate.delete(key);
        return ResponseResult.okResult();
    }
}

SecurityConfig——SpringSecurity configuration class
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 {
    
    

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//    为啥注入的不是实现类呢? 不传实现类是为了符合开闭原则
    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint; //认证成功处理器
    @Autowired
    AccessDeniedHandler accessDeniedHandler; //认证失败处理器

    @Bean
    public PasswordEncoder passwordEncoder(){
    
     //对比密码时 加密方式  要改成BCryptPasswordEncoder()
        return new BCryptPasswordEncoder();
    }

    @Override
    protected 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 interface implementation class)
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 {
    
    
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MenuMapper menuMapper;

    /**
     * 通过用户名 查找用户
     * @param username
     * @return UserDetails 对象
     */
    @Override
    public 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 login verification filter (JWT authentication filter)
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 {
    
    

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
//      登录校验过滤器        定义Jwt认证过滤器  浏览器登录后,再次发送请求时,会携带token,让服务器验证

        //获取请求头中的token
        String token = request.getHeader("token");
        if(!StringUtils.hasText(token)){
    
     //没有token就说明是第一次登录,直接放行
            //说明该接口 不需要登录,直接放行
            filterChain.doFilter(request,response);
            return;   //放行,程序到此结束
        }
        //解析token 获取userid
        Claims 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  存入SecurityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);//未认证,用两个参数,认证过,用三个参数
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

//        都执行完了要 放行,让下面的filter处理
        filterChain.doFilter(request,response);
    }
}

AuthenticationEntryPoint - Authentication success handler
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 认证失败处理器
    @Override
    public 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 - Authentication failure handler
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 授权失败处理器
    @Override
    public 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. Registration

Insert image description here

UserServiceImpl
package com.sangeng.service.impl;

import ...
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private PasswordEncoder passwordEncoder; 
    //SecurityConfig中配置类中将PasswordEncoder(对密码加密处理)注入到容器中,这里才能使用
   

   @Override
    public 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. Tools

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();
            //实现属性copy
            BeanUtils.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 -> 收集操作,泛型转成list
        return 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
{
    
    
    @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);
    }

    /**
     * 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作为文件名  生成uuid
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        //后缀和文件后缀一致
        int index = fileName.lastIndexOf("."); //找到111.png中.的索引
        // test.jpg -> .jpg
        String fileType = fileName.substring(index); //截取index后面的字符串 [index, ... )
//        拼接  2022/1/15/+uuid+.jpg 即可得到文件路径
        return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
    }
}

Guess you like

Origin blog.csdn.net/qq_45432276/article/details/132237552