• SpringSecurity(JWT、SecurityConfig、Redis)


    可能会报错Last unit does not have enough valid bits
    把jwtUtils的KEY改成偶数位

    主要用于校验,授权
    导入依赖
    org.springframework. boot
    spring-boot-starter-security

    导入以来会自动跳转登录页面,用户为user,密码在控制台,也就是打印日志记录
    它的原理就是一个过滤器链,一个过滤器,多个就是过滤器链。

    这里springboot版本用的是2.5.0
    jdk版本也不要搞错了,一定是java8

    fastjson是用于json转换的工具



    org.springframework.boot
    spring-boot-starter-data-redis



    com.alibaba
    fastjson
    1.2.33



    io.jsonwebtoken
    jjwt
    0.9.0

    关于redis的相关配置Redis使用FastJson序列化
    ```
    public class FastJsonRedisSerializer implements RedisSerializer
    {

        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

        private Class clazz;

        static
        {
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        }

        public FastJsonRedisSerializer(Class clazz)
        {
            super();
            this.clazz = clazz;
        }

        @Override
        public byte[] serialize(T t) throws SerializationException
        {
            if (t == null)
            {
                return new byte[0];
            }
            return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
        }

        @Override
        public T deserialize(byte[] bytes) throws SerializationException
        {
            if (bytes == null || bytes.length <= 0)
            {
                return null;
            }
            String str = new String(bytes, DEFAULT_CHARSET);

            return JSON.parseObject(str, clazz);
        }


        protected JavaType getJavaType(Class clazz)
        {
            return TypeFactory.defaultInstance().constructType(clazz);
        }
    }

    ```
    Redis配置
    ```

    @Configuration
    public class RedisConfig {
        @Bean
        @SuppressWarnings(value = {"unchecked","rawtypes"})
        public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory)
        {
            RedisTemplate template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);

            FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);


            //使用stringRedisserializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(serializer);

            // Hash的key也采用stringRedisSerializer的序列化方式
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(serializer);
            template.afterPropertiesSet();
            return template;
        }
    }

    ```
    统一返回值domain响应类
    ```
    import com.fasterxml.jackson.annotation.JsonInclude;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class ResponseResult {
        /**
         * 状态码
         */
        private Integer code;
        /**
         * 提示信息,如果有错误时,前端可以获取该字段进行提示
         */
        private String msg;
        /**
         * 查询到的结果数据,
         */
        private T data;

        public ResponseResult(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public ResponseResult(Integer code, T data) {
            this.code = code;
            this.data = data;
        }

        public Integer getCode() {
            return code;
        }

        public void setCode(Integer code) {
            this.code = code;
        }

        public String getMsg() {
            return msg;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }

        public T getData() {
            return data;
        }

        public void setData(T data) {
            this.data = data;
        }

        public ResponseResult(Integer code, String msg, T data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
    }

    ```
    JWT工具包

    ```
    public class JwtUtil {

        //有效期为
        public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
        //设置秘钥明文
        public static final String JWT_KEY = "fancy";

        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("fancy")     // 签发者
                    .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.imot.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 void setCacheObject(final String key, final T value)
        {
            redisTemplate.opsForValue().set(key, value);
        }

        /**
         * 缓存基本的对象,Integer、String、实体类等
         *
         * @param key 缓存的键值
         * @param value 缓存的值
         * @param timeout 时间
         * @param timeUnit 时间颗粒度
         */
        public 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 getCacheObject(final String key)
        {
            ValueOperations 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 long setCacheList(final String key, final List dataList)
        {
            Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
            return count == null ? 0 : count;
        }

        /**
         * 获得缓存的list对象
         *
         * @param key 缓存的键值
         * @return 缓存键值对应的数据
         */
        public List getCacheList(final String key)
        {
            return redisTemplate.opsForList().range(key, 0, -1);
        }

        /**
         * 缓存Set
         *
         * @param key 缓存键值
         * @param dataSet 缓存的数据
         * @return 缓存数据的对象
         */
        public BoundSetOperations setCacheSet(final String key, final Set dataSet)
        {
            BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
            Iterator it = dataSet.iterator();
            while (it.hasNext())
            {
                setOperation.add(it.next());
            }
            return setOperation;
        }

        /**
         * 获得缓存的set
         *
         * @param key
         * @return
         */
        public Set getCacheSet(final String key)
        {
            return redisTemplate.opsForSet().members(key);
        }

        /**
         * 缓存Map
         *
         * @param key
         * @param dataMap
         */
        public void setCacheMap(final String key, final Map dataMap)
        {
            if (dataMap != null) {
                redisTemplate.opsForHash().putAll(key, dataMap);
            }
        }

        /**
         * 获得缓存的Map
         *
         * @param key
         * @return
         */
        public Map getCacheMap(final String key)
        {
            return redisTemplate.opsForHash().entries(key);
        }

        /**
         * 往Hash中存入数据
         *
         * @param key Redis键
         * @param hKey Hash键
         * @param value 值
         */
        public 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 getCacheMapValue(final String key, final String hKey)
        {
            HashOperations opsForHash = redisTemplate.opsForHash();
            return opsForHash.get(key, hKey);
        }

        /**
         * 删除Hash中的数据
         * 
         * @param key
         * @param hkey
         */
        public void delCacheMapValue(final String key, final String hkey)
        {
            HashOperations hashOperations = redisTemplate.opsForHash();
            hashOperations.delete(key, hkey);
        }

        /**
         * 获取多个Hash中的数据
         *
         * @param key Redis键
         * @param hKeys Hash键集合
         * @return Hash对象集合
         */
        public List getMultiCacheMapValue(final String key, final Collection hKeys)
        {
            return redisTemplate.opsForHash().multiGet(key, hKeys);
        }

        /**
         * 获得缓存的基本对象列表
         *
         * @param pattern 字符串前缀
         * @return 对象列表
         */
        public Collection keys(final String pattern)
        {
            return redisTemplate.keys(pattern);
        }
    }

    ```

    WebUtils
    ```
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    public class WebUtils
    {
        /**
         * 将字符串渲染到客户端
         * 
         * @param response 渲染对象
         * @param string 待渲染的字符串
         * @return null
         */
        public static String renderString(HttpServletResponse response, String string) {
            try
            {
                response.setStatus(200);
                response.setContentType("application/json");
                response.setCharacterEncoding("utf-8");
                response.getWriter().print(string);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            return null;
        }
    }

    ```

    这里注意如果在数据库中的密码前面加上{noop}即可让密码明文

    重写一个UserDetailsServiceImpl去实现UserDetailsService重写方法loadUserByUsername,返回值是UserDetails

    ```

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {


        @Autowired
        private UserMapper userMapper;
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

            //查询用户信息

            LambdaQueryWrapper userLambdaQueryWrapper = new LambdaQueryWrapper<>();
            userLambdaQueryWrapper.eq(User::getUserName,username);

            User user = userMapper.selectOne(userLambdaQueryWrapper);

            if (Objects.isNull(user)){
                throw new RuntimeException("用户名或密码错误");
            }
            //TODO 查询对应的权限信息


    ```

    创建一个类LoginUser来实现返回值UserDetails

    ```
    package com.imot.pojo.entity;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;

    import java.util.Collection;


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class LoginUser implements UserDetails {

        private User user;


        @Override
        public Collection getAuthorities() {
            return null;
        }

        @Override
        public String getPassword() {
            return user.getPassword();
        }

        @Override
        public String getUsername() {
            return user.getUserName();
        }

        @Override
        public boolean isAccountNonExpired() {
            return true;
        }

        @Override
        public boolean isAccountNonLocked() {
            return true;
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }

        @Override
        public boolean isEnabled() {
            return true;
        }
    }


    ```

    加密方法为
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String  encode = passwordEncoder.encode("1234");

    passwordEncoder.matches("明文","密文");    //可查看是否相等返回一个布尔值

    ```


    ```


    因为已经封装好Bean的SecurityConfig,需要调用直接autowaired

    jwt依赖
    ```


    io.jsonwebtoken
    jjwt
    0.9.0

    ```


    **SecurityConfig**
    ```

    package com.imot.config;

    import com.imot.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.authentication.UsernamePasswordAuthenticationFilter;

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }

        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //关闭csrf
                    .csrf().disable()
                    //不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 对于登录接口 允许匿名访问
                    .antMatchers("/user/login").anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        }

        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }

    ```
    jwtUtils工具中
    createJWt("123")就是生成token的方法
    这就是解析token,subject即是解密
    Claims claims= parseJT("eyJhbGci0iJ“);
    String subject = claims.getSubject();

    在SecurityConfig中重写方法authenticationManagerBean,按住alt+insert

    **写一个登录的方法**
    ```
        @PostMapping("/user/login")
        @ApiOperation("测试登录")
        public ResponseResult login(@RequestBody User user) {
            //登录


            return loginService.login(user);
        }
    ```

    **LoginServiceImpl**
    ```
    package com.imot.service.impl;

    import com.imot.domain.ResponseResult;
    import com.imot.pojo.entity.LoginUser;
    import com.imot.pojo.entity.User;
    import com.imot.service.LoginService;
    import com.imot.utils.JwtUtil;
    import com.imot.utils.RedisCache;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.stereotype.Service;

    import java.util.HashMap;
    import java.util.Objects;

    @Service
    public class LoginServiceImpl implements LoginService {

        @Autowired
        private AuthenticationManager authenticationManager;

        @Autowired
        private RedisCache redisCache;
        @Override
        public ResponseResult login(User user) {
            //AuthenticationManager进行用户认证
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());


            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);

            //判断null就验证没有通过,不为null就认证通过了
            if (Objects.isNull(authenticate)){
                throw new RuntimeException("登陆失败");
            }

            //如果认证通过了,使用userid去生成一个jwt存入返回值responseResult返回

            //使用userid生成token
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
            String userId = loginUser.getUser().getId().toString();
            String jwt = JwtUtil.createJWT(userId);
            //authenticate存入redis
            redisCache.setCacheObject("login:"+userId,loginUser);
            //把token响应给前端
            HashMap map = new HashMap<>();
            map.put("token",jwt);

            return new ResponseResult(200,"登陆成功",map);
        //

        }
    }

    ```

    Jwt过滤器
    ```
    package com.imot.filter;

    import com.imot.pojo.entity.LoginUser;
    import com.imot.utils.JwtUtil;
    import com.imot.utils.RedisCache;
    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 {
            //获取token
            String token = request.getHeader("token");
            if (!StringUtils.hasText(token)) {
                //放行
                filterChain.doFilter(request, response);
                return;
            }
            //解析token
            String userid;
            try {
                Claims claims = JwtUtil.parseJWT(token);
                userid = claims.getSubject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("token非法");
            }
            //从redis中获取用户信息
            String redisKey = "login:" + userid;
            LoginUser loginUser = redisCache.getCacheObject(redisKey);
            if(Objects.isNull(loginUser)){
                throw new RuntimeException("用户未登录");
            }
            //存入SecurityContextHolder
            //TODO 获取权限信息封装到Authentication中
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(loginUser,null,null);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            //放行
            filterChain.doFilter(request, response);
        }
    }
    ```


    退出controller
    ```
        @RequestMapping("/user/logout")
        @ApiOperation("测试退出")
        public ResponseResult logout(@RequestBody User user) {
            //登录


            return loginService.logout();
        }

    ```
    serviceImpl
    ```

        @Override
        public ResponseResult logout() {
            UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            Long userid = loginUser.getUser().getId();
            redisCache.deleteObject("login:"+userid);
            return new ResponseResult(200,"退出成功");
        }
    ```


     

  • 相关阅读:
    基于PHP的学生在线成绩管理系统
    百分比组件
    OpenCV读取图像时按照BGR的顺序HWC排列,PyTorch按照RGB的顺序CHW排列
    麻将馆电脑计费系统,棋牌室怎么用电脑控制灯计时,佳易王计时计费系统软件下载
    springboot多用户B2C商城平台系统在线视频点播系统毕业设计毕设作品开题报告开题答辩PPT
    http协议详解01——http协议概念及工作流程
    typedef struct 与 struct 的区别
    using virtualbox in ubuntu
    imedicallis命令的背后
    超好用,分享8个 Python 自动化脚本
  • 原文地址:https://blog.csdn.net/weixin_47467550/article/details/139723434