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 {

    @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配置类

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接口实现类)

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 登录校验过滤器 (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 {

    @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 ——认证成功处理器

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 ——认证失败处理器

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、注册

在这里插入图片描述

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、工具类

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();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Xcode 基座打包

Xcode基座打包-APP更新版本内容无效 问题&#xff1a;解决&#xff1a; 问题&#xff1a; 使用xcode基座打包之后&#xff0c;上传到appstore进行提审发布。 用户在appstore商城进行更新下载&#xff0c;打开更新后的APP发现版本号是最新的&#xff0c;APP里面的其他内容还是上…

HTML5基础

1、HTML5概述 2014年10月28日&#xff0c;W3C&#xff08;world wide web consortium&#xff0c;万维网联盟&#xff09;的HTML工作组发布了HTML5的正式推荐标准。HTML5作为构建开放Web平台的核心&#xff0c;增加了支持Web应用的许多新特性&#xff0c;以及更符合开发者使用…

Arcgis地图实战二:地图实时轨迹展示

1.最终效果预览 2.定时器执行方法 进入页面执行执行器 this.locationInterval setInterval(() > {this.getCurrentPosition();}, this.conf.LocateInterval);离开页面销毁 clearInterval(this.locationInterval);this.conf.LocateInterval为获取的数据同步中的定时器间隔…

你的服务器安全吗?--服务器防渗透

1、概述 在本人所处的公司的服务器正式遭到黑客攻击之前&#xff0c;一直都以为 黑客 是个遥不可及的词&#xff0c;直到真正成为了受害者时&#xff0c;才猛然意识到安全的重要性。有一些基本经验和心得总结出来&#xff0c;和同行分享一下吧。 2、暴破手段 最粗暴的黑客行为…

C++ 多态性——虚函数

虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生之后&#xff0c;在类族中就可以实现运行过程的多态。 根据类型兼容规则&#xff0c;可以使用派生类的对象代替基类的对象。如果基类类型的指针指向派生类对象&#xff0c;就可以通过这个指针来访问该对…

用ChatGPT和六顶帽思考法帮助自己更好地决策和解决问题

当我们在解决复杂问题时&#xff0c;我们常常陷入单一视角的状态。创造性思维领域的先驱爱德华德博诺&#xff0c;提出了六顶帽思考法[1]&#xff0c;这意味着我们可以从六个不同的视角来思考一个问题&#xff0c;以实现高水平决策和解决问题。 每一顶“帽子”代表不同的视角。…

【写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串】

写一个函数&#xff0c;判断一个字符串是否为另外一个字符串旋转之后的字符串 1.题目 写一个函数&#xff0c;判断一个字符串是否为另外一个字符串旋转之后的字符串。 例如&#xff1a;给定s1 AABCD和s2 BCDAA&#xff0c;返回1 给定s1abcd和s2ACBD&#xff0c;返回0. AABCD左…

当前服务器版本不支持该功能,请联系经销商升级服务器 - - 达梦数据库报错

当前服务器版本不支持该功能&#xff0c;请联系经销商升级服务器 - - 达梦数据库报错 环境介绍1 搭建测试环境2 报错内容3 标准版介绍 环境介绍 某项目使用标准版数据库中&#xff0c;使用insert into 正常操作表&#xff0c;插入数据时报错&#xff0c;表为普通表。 1 搭建测…

sharedPreferences的使用之按钮状态切换的保存

什么是sharedPreferences&#xff1f;有什么用 SharedPreference是Android开发中一个轻量级的数据存储的方式&#xff0c;除了它还有SQLite数据库。它可以将数据以键值对的形式存放到文件中&#xff0c;在需要的时候再取出来使用。相比于去操作数据库&#xff0c;对于一些简单…

3.1 Spring MVC概述

1. MVC概念 MVC是一种编程思想&#xff0c;它将应用分为模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、控制器&#xff08;Controller&#xff09;三个层次&#xff0c;这三部分以最低的耦合进行协同工作&#xff0c;从而提高应用的可扩展性及可维护…

基于Flask的模型部署

基于Flask的模型部署 一、背景 Flask&#xff1a;一个使用Python编写的轻量级Web应用程序框架&#xff1b; 首先需要明确模型部署的两种方式&#xff1a;在线和离线&#xff1b; 在线&#xff1a;就是将模型部署到类似于服务器上&#xff0c;调用需要通过网络传输数据&…

大模型的数据隐私问题有解了,浙江大学提出联邦大语言模型

作者 | 小戏、Python 理想化的 Learning 的理论方法作用于现实世界总会面临着诸多挑战&#xff0c;从模型部署到模型压缩&#xff0c;从数据的可获取性到数据的隐私问题。而面对着公共领域数据的稀缺性以及私有领域的数据隐私问题&#xff0c;联邦学习&#xff08;Federated Le…

AI:02-基于深度学习的动物图像检索算法的研究

文章目录 一、算法原理二、代码实现三、实验结果四、总结深度学习在计算机视觉领域中的应用越来越广泛,其中动物图像检索算法是一个重要的应用场景。本文将介绍一种基于深度学习的动物图像检索算法,并提供相应的代码实现。 一、算法原理 本算法采用卷积神经网络(Convolutio…

如何将maven部署在Idea的教学,和idea介绍

目录 一.idea介绍&#xff0c;以及一些基本特点&#xff01; 1.1idea介绍 1.2idea特点 1.3.idea和eclipse区别 1.4idea安装 4.1下载网址 4.2下载后安装&#xff08;见图片&#xff09; 1.5 Idea的一些操作更改 5.1主题颜色 5.2设置鼠标悬浮提示 5.3显示方法分隔符 5.4忽…

架构设计第八讲:架构 - 理解架构的模式2 (重点)

架构设计第八讲&#xff1a;架构 - 理解架构的模式2 (重点) 本文是架构设计第8讲&#xff1a;架构 - 理解架构的模式2&#xff0c;整理自朱晔的互联网架构实践心得, 他是结合了 微软给出的云架构的一些模式的基础上加入他自己的理解来总结互联网架构中具体的一些模式。我在此基…

Databend 开源周报第 105 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 Databend 轻量级…

netty面试题2

1、一次完整的HTTP请求的所经历的步骤 1、首先进行DNS域名解析&#xff08;本地浏览器缓存、操作系统缓存或者DNS服务器&#xff09;&#xff0c;首先会搜索浏览器自身的DNS缓存&#xff08;缓存时间比较短&#xff0c;大概只有1分钟&#xff0c;且只能容纳1000条缓存&#xff…

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

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

知识图谱实战应用23-【知识图谱的高级用法】Neo4j图算法的Cypher查询语句实例

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用23-【知识图谱的高级用法】Neo4j图算法的Cypher查询语句实例,Neo4j图算法是一套在Neo4j图数据库上运行的算法集合。这些算法专门针对图数据结构进行设计,用于分析、查询和处理图数据。图算法可以帮助我们发现图中的模…

Anaconda Prompt使用pip安装PyQt5-tools后无法打开Spyder或闪退

艹&#xff01;MLGBZD! 真TMD折腾人&#xff01; 出现原因&#xff1a; 首次安装完Anaconda3-2023.07-1-Windows-x86_64.exe后首次打开Spyder&#xff0c;此时是没有问题的&#xff0c;然后打开Anaconda Prompt&#xff0c;查看有哪些包&#xff0c;pip list 这时候开始首次安…