• SpringBoot整合SpringSecrity+JWT ---复制即用


    在这里插入图片描述
    — 需要的技术 —

    技术说明官网
    springbootMVC框架https://spring.io/projects/spring-boot
    mybatis-plusORM框架https://baomidou.com
    SpringSecurity认证和授权框架https://spring.io/projects/spring-security
    Redis分布式缓存https://redis.io/
    JWTJWT登录支持https://github.com/jwtk/jjwt

    — 身份认证流程 —
    在这里插入图片描述
    — 依赖 —

            
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-parentartifactId>
                    <version>2.6.4version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
                
                <dependency>
                    <groupId>com.baomidougroupId>
                    <artifactId>mybatis-plus-boot-starterartifactId>
                    <version>3.4.2version>
                dependency>
                <dependency>
                    <groupId>com.baomidougroupId>
                    <artifactId>mybatis-plus-generatorartifactId>
                    <version>3.4.1version>
                dependency>
                <dependency>
                    <groupId>org.freemarkergroupId>
                    <artifactId>freemarkerartifactId>
                    <version>2.3.28version>
                dependency>
                            
                
                <dependency>
                    <groupId>com.alibabagroupId>
                    <artifactId>druidartifactId>
                    <version>1.2.8version>
                dependency>
                        
                <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
                 dependency>
                 <dependency>
                    <groupId>org.apache.velocitygroupId>
                    <artifactId>velocity-engine-coreartifactId>
                    <version>2.2version>
                dependency>
                
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-securityartifactId>
                    <version>2.6.4version>
                dependency>
               
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-data-redisartifactId>
                    <version>2.6.4version>
                dependency>
               
                <dependency>
                    <groupId>org.springframework.securitygroupId>
                    <artifactId>spring-security-jwtartifactId>
                    <version>1.0.10.RELEASEversion>
                dependency>
                <dependency>
                    <groupId>io.jsonwebtokengroupId>
                    <artifactId>jjwtartifactId>
                    <version>0.9.0version>
                dependency>
             
                <dependency>
                    <groupId>org.projectlombokgroupId>
                    <artifactId>lombokartifactId>
                    <version>1.16.10version>
                dependency>
                  <dependency>
                    <groupId>com.alibabagroupId>
                    <artifactId>fastjsonartifactId>
                    <version>1.2.47version>
                dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    — yml配置 —

    server:
      port: 8081
    
    spring:
      application:
        name: manage-client
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/test?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
        username: root
        password: root
      main:
        allow-circular-references: true
      redis:
        database: 0
        host: 127.0.0.1
        port: 6379
        lettuce:
          pool:
            max-active: 100
            max-idle: 300
            max-wait: 10000
          cluster:
            refresh:
              adaptive: true
        timeout: 5000
    
    #mybatis-plus配置
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      mapper-locations: classpath:com/huangyabei/mapper/xml/*.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    — 工具类 —
    JwtUtil

    import io.jsonwebtoken.*;
    import io.jsonwebtoken.io.Decoders;
    import io.jsonwebtoken.security.Keys;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;
    
    import javax.crypto.SecretKey;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    @Slf4j
    @Component
    //@ConfigurationProperties(prefix = "jwt")
    public class JwtUtil {
        /**
         * 携带JWT令牌的HTTP的Header的名称,在实际生产中可读性越差越安全
         */
    //    @Getter
    //    @Value("${jwt.header}")
        private String header = "Authorization";
    
        /**
         * 为JWT基础信息加密和解密的密钥
         * 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改。
         */
    //    @Value("${jwt.secret}")
        private String secret = "guYloAPAmKwvKq4a5f5dqnifiQatxMEPNOvtwPsJPQWLNKJDCXZ";
    
        /**
         * JWT令牌的有效时间,单位秒
         * - 默认1周
         */
    //    @Value("${jwt.expiration}")
        private Long expiration = 604800L;
    
        /**
         * SecretKey 根据 SECRET 的编码方式解码后得到:
         * Base64 编码:SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
         * Base64URL 编码:SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString));
         * 未编码:SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
         */
        private static SecretKey getSecretKey(String secret) {
            byte[] encodeKey = Decoders.BASE64.decode(secret);
            return Keys.hmacShaKeyFor(encodeKey);
        }
    
        /**
         * 用claims生成token
         *
         * @param claims 数据声明,用来创建payload的私有声明
         * @return token 令牌
         */
        private String generateToken(Map<String, Object> claims) {
            SecretKey key = getSecretKey(secret);
            //SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //两种方式等价
    
            // 添加payload声明
            JwtBuilder jwtBuilder = Jwts.builder()
                    // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                    .setClaims(claims)
                    // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                    .setId(UUID.randomUUID().toString())
                    // iat: jwt的签发时间
                    .setIssuedAt(new Date())
    
                    // 你也可以改用你喜欢的算法,支持的算法详见:https://github.com/jwtk/jjwt#features
                    // SignatureAlgorithm.HS256:指定签名的时候使用的签名算法,也就是header那部分
                    .signWith(SignatureAlgorithm.HS256, key)
                    .setExpiration(new Date(System.currentTimeMillis() + this.expiration * 1000));
    
            String token = jwtBuilder.compact();
            return token;
        }
    
        /**
         * 生成Token令牌
         *
         * @param userDetails 用户
         * @param id          用户编号
         * @return 令牌Token
         */
        public  String generateToken(UserDetails userDetails, String id) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("userId", id);
            claims.put("sub", userDetails.getUsername());
            claims.put("created", new Date());
            return generateToken(claims);
        }
    
        /**
         * 从token中获取数据声明claim
         *
         * @param token 令牌token
         * @return 数据声明claim
         */
        public Claims getClaimsFromToken(String token) {
            try {
                SecretKey key = getSecretKey(secret);
                Claims claims = Jwts.parser()
                        .setSigningKey(key)
                        .parseClaimsJws(token)
                        .getBody();
                return claims;
            } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
                log.error("token解析错误", e);
                throw new IllegalArgumentException("Token invalided.");
            }
        }
    
        public String getUserId(String token) {
            return (String) getClaimsFromToken(token).get("userId");
        }
    
        /**
         * 从token中获取登录用户名
         *
         * @param token 令牌
         * @return 用户名
         */
        public String getSubjectFromToken(String token) {
            String subject;
            try {
                Claims claims = getClaimsFromToken(token);
                subject = claims.getSubject();
            } catch (Exception e) {
                subject = null;
            }
            return subject;
        }
    
    
        /**
         * 获取token的过期时间
         *
         * @param token token
         * @return 过期时间
         */
        public Date getExpirationFromToken(String token) {
            return getClaimsFromToken(token).getExpiration();
        }
    
        /**
         * 判断token是否过期
         *
         * @param token 令牌
         * @return 是否过期:已过期返回true,未过期返回false
         */
        public Boolean isTokenExpired(String token) {
            Date expiration = getExpirationFromToken(token);
            return expiration.before(new Date());
        }
    
        /**
         * 验证令牌:判断token是否非法
         *
         * @param token       令牌
         * @param userDetails 用户
         * @return 如果token未过期且合法,返回true,否则返回false
         */
        public Boolean validateToken(String token, UserDetails userDetails) {
            //如果已经过期返回false
            if (isTokenExpired(token)) {
                return false;
            }
            String usernameFromToken = getSubjectFromToken(token);
            String username = userDetails.getUsername();
            return username.equals(usernameFromToken);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173

    Redis
    RedisConfig

    import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(RedisProperties.class)
    public class RedisConfig {
    
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            //使用fastjson序列化
            FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
            // value值的序列化采用fastJsonRedisSerializer
            template.setValueSerializer(fastJsonRedisSerializer);
            template.setHashValueSerializer(fastJsonRedisSerializer);
            // key的序列化采用StringRedisSerializer
            template.setKeySerializer(new StringRedisSerializer());
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        @Bean
        @ConditionalOnMissingBean(StringRedisTemplate.class)
        public StringRedisTemplate stringRedisTemplate(
                RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    RedisUtil

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.*;
    import org.springframework.stereotype.Component;
    
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    @Component
    public final class RedisUtil {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         * 获得缓存的基本对象列表
         *
         * @param pattern 字符串前缀
         * @return 对象列表
         */
        public Collection<String> keys(final String pattern)
        {
            try {
                return redisTemplate.keys(pattern);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    
        /**
         * 指定缓存失效时间
         * @param key 键
         * @param time 时间(秒)
         * @return
         */
        public boolean expire(final String key, long time) {
            try {
                if (time > 0) {
                    redisTemplate.expire(key, time, TimeUnit.SECONDS);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 设置有效时间
         *
         * @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);
        }
    
        /**
         * 根据key 获取过期时间
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(final String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
        /**
         * 判断key是否存在
         * @param key 键
         * @return true 存在 false不存在
         */
        public boolean hasKey(final String key) {
            try {
                return redisTemplate.hasKey(key);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 删除单个对象
         *
         * @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);
        }
    
        /**
         * 普通缓存获取
         * @param key 键
         * @return 值
         */
        public <T> T get(final String key) {
            ValueOperations<String, T> operation = redisTemplate.opsForValue();
            return key == null ? null : operation.get(key);
        }
        /**
         * 普通缓存放入
         * @param key 键
         * @param value 值
         * @return true成功 false失败
         */
        public <T> boolean set(final String key,final T value) {
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 普通缓存放入, 不存在放入,存在返回
         * @param key 键
         * @param value 值
         * @return true成功 false失败
         */
        public <T> boolean setnx(final String key,final T value) {
            try {
                redisTemplate.opsForValue().setIfAbsent(key,value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public <T> boolean set(final String key,final T value,final long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 普通缓存放入并设置时间,不存在放入,存在返回
         * @param key 键
         * @param value 值
         * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public <T> boolean setnx(final String key,final T value,final long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 递增
         * @param key 键
         * @param delta 要增加几(大于0)
         * @return
         */
        public long incr(final String key,final long delta) {
            if (delta < 0) {
                throw new RuntimeException("递增因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
        /**
         * 递减
         * @param key 键
         * @param delta 要减少几(小于0)
         * @return
         */
        public long decr(final String key,final long delta) {
            if (delta < 0) {
                throw new RuntimeException("递减因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
        /**
         * HashGet
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return 值
         */
        public <T> T hget(final  String key,final  String item) {
            HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
            return opsForHash.get(key, item);
        }
        /**
         * 获取hashKey对应的所有键值
         * @param key 键
         * @return 对应的多个键值
         */
        public <T> Map<String, T> hmget(final String key) {
            return redisTemplate.opsForHash().entries(key);
        }
        /**
         * HashSet
         * @param key 键
         * @param map 对应多个键值
         * @return true 成功 false 失败
         */
        public <T> boolean hmset(final String key,final Map<String, T> map) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * HashSet 并设置时间
         * @param key 键
         * @param map 对应多个键值
         * @param time 时间(秒)
         * @return true成功 false失败
         */
        public <T> boolean hmset(final String key,final Map<String, T> map,final long time) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @return true 成功 false失败
         */
        public <T> boolean hset(final String key,final String item,final T value) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @return true 成功 false失败
         */
        public <T> boolean hset(final String key,final String item,final T value,final long time) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 删除hash表中的值
         * @param key 键 不能为null
         * @param item 项 可以使多个 不能为null
         */
        public void hdel(final String key, Object... item) {
            redisTemplate.opsForHash().delete(key, item);
        }
        /**
         * 判断hash表中是否有该项的值
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false不存在
         */
        public boolean hHasKey(final String key,final String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
        /**
         * hash递增 如果不存在,就会创建一个 并把新增后的值返回
         * @param key 键
         * @param item 项
         * @param by 要增加几(大于0)
         * @return
         */
        public double hincr(final String key,final String item,final double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
        /**
         * hash递减
         * @param key 键
         * @param item 项
         * @param by 要减少记(小于0)
         * @return
         */
        public double hdecr(final String key,final String item,final double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
        /**
         * 根据key获取Set中的所有值
         * @param key 键
         * @return
         */
        public <T> Set<T> sGet(final String key) {
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 根据value从一个set中查询,是否存在
         * @param key 键
         * @param value 值
         * @return true 存在 false不存在
         */
        public <T> boolean sHasKey(final String key,final T value) {
            try {
                if (null == value){
                    return false;
                }
                SetOperations setOperations = redisTemplate.opsForSet();
                return setOperations.isMember(key, value);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将数据放入set缓存
         * @param key 键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public <T> BoundSetOperations<String, T> sSet(final String key, final Set<T> dataSet) {
            try {
                BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
                Iterator<T> it = dataSet.iterator();
                while (it.hasNext())
                {
                    setOperation.add(it.next());
                }
                return setOperation;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 将set数据放入缓存
         * @param key 键
         * @param time 时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public <T> BoundSetOperations<String, T> sSetAndTime(final String key,final long time,final Set<T> dataSet) {
            try {
                BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
                Iterator<T> it = dataSet.iterator();
                while (it.hasNext())
                {
                    setOperation.add(it.next());
                }
                if (time > 0)
                    expire(key, time);
                return setOperation;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 获取set缓存的长度
         * @param key 键
         * @return
         */
        public long sGetSetSize(final String key) {
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        /**
         * 移除值为value的
         * @param key 键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(final String key,Object... values) {
            try {
                Long count = redisTemplate.opsForSet().remove(key, values);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // ===============================list=================================
        /**
         * 获取list缓存的内容
         * @param key 键
         * @param start 开始
         * @param end 结束 0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            try {
                return redisTemplate.opsForList().range(key, start, end);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 获取list缓存的长度
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            try {
                return redisTemplate.opsForList().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        /**
         * 通过索引 获取list中的值
         * @param key 键
         * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
         * @return
         */
        public Object lGetIndex(String key, long index) {
            try {
                return redisTemplate.opsForList().index(key, index);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, Object value) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @param time 时间(秒)
         * @return
         */
        public boolean lSet(String key, Object value, long time) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                if (time > 0)
                    expire(key, time);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, List<Object> value) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将list放入缓存
         *
         * @param key 键
         * @param value 值
         * @param time 时间(秒)
         * @return
         */
        public boolean lSet(String key, List<Object> value, long time) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                if (time > 0)
                    expire(key, time);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 根据索引修改list中的某条数据
         * @param key 键
         * @param index 索引
         * @param value 值
         * @return
         */
        public boolean lUpdateIndex(String key, long index, Object value) {
            try {
                redisTemplate.opsForList().set(key, index, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 移除N个值为value
         * @param key 键
         * @param count 移除多少个
         * @param value 值
         * @return 移除的个数
         */
        public long lRemove(String key, long count, Object value) {
            try {
                Long remove = redisTemplate.opsForList().remove(key, count, value);
                return remove;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579

    WebUtil

    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    
    public class WebUtil {
    
        /**
         * 将字符串渲染到客户端
         * @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, ServletContext context, HttpServletResponse response) throws UnsupportedEncodingException {
            String mimeType = context.getMimeType(filename);//获取文件的mime类型
            response.setHeader("content-type",mimeType);
            String fname= URLEncoder.encode(filename,"UTF-8");
            response.setHeader("Content-disposition","attachment; filename="+fname);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    -----------------------------------------核心代码部分--------------------------------------------
    我们可以自定义一个UserDetailsService,让SpringSecrity使用我们的UserDetailsService,我们自己的UserDetailsService可以从数据库中查询用户名/密码/用户权限
    — Entity —
    普通数据库表实体类(可以根据自己业务需要设置user实体)

    /**
     * 

    * 用户表 *

    * * @author car-hailing-saas * @since 2022-04-12 */
    @Data @TableName("t_tenant_user") public class TenantUserEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 用户id */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 用户名 */ private String userName; /** * 姓名 */ private String realName; /** * 密码 */ private String passWord; /** * 租户ID */ private Long tenantId; /** * 性别 0男 1女 */ private Integer sex; /** * 手机号 */ private String mobile; /** * 状态:(0:禁用,1:正常) */ private Integer state; /** * 是否为超级管理员(0:否,1:是) */ private Integer ifSuper; /** * 头像 */ private String headImage; /** * 邮箱 */ private String email; /** * 联系电话 */ private String telephone; /** * 公司所在城市编号 */ private String cityNo; /** * 公司详细地址 */ private String detailedAddress; /** * 组织机构ID */ private Long organizationId; /** * 职位ID */ private Long positionId; /** * 工号 */ private String jobNumber; /** * 是否首次修改密码 0 首次 1 非首次 */ private Integer firstUpdatePwd; /** *删除状态(0:正常,1:删除) */ private Integer delState; /** * 创建人ID */ private Long createBy; /** * 创建时间 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8") private Date createTime; /** * 更新者ID */ private Long updateBy; /** * 更新的时间 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8") private Date updateTime; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134

    自定义实现 SpringSecrity的 UserDetails 接口的实体

    import com.alibaba.fastjson.annotation.JSONField;
    import com.car.hailing.saas.model.entity.TenantUserEntity;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.util.CollectionUtils;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Data
    @NoArgsConstructor
    public class LoginTenantUserEntity implements UserDetails {
    
        /**
         *用户对象(这里是自己的用户实体,对应数据库中user的实体,根据需要进行修改)
         */
        private TenantUserEntity tenantUserEntity;
    
        /**
         * 权限集合(用来存从数据库查到的用户权限))
         */
        private List<String> permissions;
    
        //忽略序列化   SpringSecrity权限校验需要的格式的权限集合
        @JSONField(serialize = false)
        private List<SimpleGrantedAuthority> authorities;
    
        public LoginTenantUserEntity(TenantUserEntity tenantUserEntity, List<String> permissions) {
            this.tenantUserEntity = tenantUserEntity;
            this.permissions = permissions;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if (!CollectionUtils.isEmpty(authorities)){
                return authorities;
            }
            //把 permissions 中的String类型权限信息,封装成SimpleGrantedAuthority对象
            authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            return authorities;
        }
    
        @Override
        public String getPassword() {
            //将用户实体中的用户密码指向 UserDetails的getPassword方法
            return tenantUserEntity.getPassWord();
        }
    
        @Override
        public String getUsername() {
            //将用户实体中的用户名指向 UserDetails的getPassword方法
            return tenantUserEntity.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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    — UserDetailsServiceImpl —

    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.car.hailing.saas.dao.TenantUserMapper;
    import com.car.hailing.saas.manage.entity.LoginTenantUserEntity;
    import com.car.hailing.saas.model.entity.TenantUserEntity;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    import java.util.Objects;
    
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
        //根据自己业务需要定义查询接口
        @Resource
        TenantUserMapper tenantUserMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //查询用户信息 根据自己业务需要定义查询接口
            QueryWrapper<TenantUserEntity> qw = new QueryWrapper<>();
            qw.eq("user_name",username);
            TenantUserEntity tenantUserEntity = tenantUserMapper.selectOne(qw);
            if (Objects.isNull(tenantUserEntity)){
               throw new UsernameNotFoundException("用户名未找到");
            }
            // TODO 查询对应的权限信息 根据自己业务需要定义查询接口
            QueryWrapper<TenantUserEntity> qwMenys = new QueryWrapper<>();
            qwMenys.eq("user_id",tenantUserEntity.getId());
            List<String> list = tenantUserMapper.selectMenusList(qwMenys);
            return new LoginTenantUserEntity(tenantUserEntity,list);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    JwtAuthenticationTokenFilter 过滤器

    import com.alibaba.fastjson.JSON;
    import com.car.hailing.saas.manage.entity.LoginTenantUserEntity;
    import com.car.hailing.saas.utils.JwtUtil;
    import com.car.hailing.saas.utils.redis.RedisUtil;
    import io.jsonwebtoken.Claims;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.OncePerRequestFilter;
    import javax.annotation.Resource;
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Objects;
    
    /**
     * 认知过滤器
     */
    
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Resource
        JwtUtil jwtUtil;
        @Resource
        RedisUtil redisUtil;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            //1.获取token
            String token = request.getHeader("token");
            if (!StringUtils.hasText(token)){
                //这里的放行是请求中没有token而放行,比如登录接口这种白名单接口,会继续执行后面的过滤器
                filterChain.doFilter(request,response);
                return;
            }
            //2.解析token
            Claims claims = jwtUtil.getClaimsFromToken(token);
            Long userId = Long.valueOf(String.valueOf(claims.get("userId")));
            //3.从redis中获取用户信息
            Object userJson = redisUtil.hget("USER_TOKEN", userId + token);
            //校验redis中是否存在user信息
            if (Objects.isNull(userJson)){
                throw new RuntimeException("用户未登录");
            }
            LoginTenantUserEntity loginTenantUserEntity = JSON.parseObject(String.valueOf(userJson), LoginTenantUserEntity.class);
            //存入 SecurityContextHolder
            // TODO 获取权限信息封装到 Authentication 中
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(loginTenantUserEntity,null,loginTenantUserEntity.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    
            //对请求放行
            //这里的放行是已经对用户身份信息校验完毕了进行放行
            filterChain.doFilter(request,response);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    AuthenticationEntryPointHandler 授权认证异常处理器

    import com.alibaba.fastjson.JSON;
    import com.car.hailing.saas.base.CommonResult;
    import com.car.hailing.saas.manage.utils.WebUtil;
    import org.apache.http.HttpStatus;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.core.AuthenticationException;
    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;
    
    /**
     * security 授权认证异常处理器
     */
    
    @Component
    public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
        /**
         *身份认证失败处理
         */
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            //处理异常
            WebUtil.renderString(response, JSON.toJSONString(new CommonResult<>(HttpStatus.SC_UNAUTHORIZED,"用户认证失败!")));
    
        }
    
        /**
         *权限认证失败处理
         */
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            //处理异常
            WebUtil.renderString(response, JSON.toJSONString(new CommonResult<>(HttpStatus.SC_FORBIDDEN,"用户权限不足!")));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    **SecrityConfig 配置类 **

    import com.car.hailing.saas.manage.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.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)  //启用Spring Security Global方法安全性类
    public class SecrityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 密码加密/校验
         */
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        @Autowired
        JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
        @Autowired
        AccessDeniedHandler accessDeniedHandler;
        @Autowired
        AuthenticationEntryPoint authenticationEntryPoint;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //关闭csrf
                    .csrf().disable()
                    //不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // anonymous()允许匿名访问 // permitAll() 任何人都可以访问  // denyAll() 任何人都不允许访问
                    // 基于注解配置接口权限 hasAuthority() 单个权限 hasAnyAuthority()多个权限
                    .antMatchers("/index/login").anonymous()
                    //除上面外的所有请求全部需要鉴权认证,指定任何身份验证的用户允许使用URL
                    .anyRequest().authenticated();
    
            //将自定义的认证过滤器添加到secruty过滤器链最前面的位置
            http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
    
            //添加异常处理器
            http.exceptionHandling()
                    //认证失败处理器
                    .authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler);
    
            //允许跨越
            http.cors();
    
        }
    
        /**
         * 身份验证管理器
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    CorsConfig Spring boot 跨域配置类

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * Spring boot 跨域配置类
     */
    
    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            //设置允许跨越的路径
            registry.addMapping("/**")
                    //设置允许跨越请求的域名
                    .allowedOriginPatterns("*")
                    //是否允许cookie
                    .allowCredentials(true)
                    //设置允许的请求方式
                    .allowedMethods("GET","POST","DELETE","PUT")
                    //设置允许的header属性
                    .allowedHeaders("*")
                    //跨越允许时间 配置客户可以通过客户端缓存的飞行前请求的响应时间。
                    //默认情况下,这将设置为1800秒
                    .maxAge(900);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    ---------模拟登录登出接口实现----------

    Controller

    import com.car.hailing.saas.base.CommonResult;
    import com.car.hailing.saas.manage.entity.LoginParam;
    import com.car.hailing.saas.manage.service.LoginService;
    import com.google.common.collect.ImmutableMap;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    
    @RestController
    @RequestMapping("/index")
    public class LoginController {
    
        @Autowired
        public LoginService loginService;
    
        @RequestMapping(value = "/login",method = RequestMethod.POST)
        private CommonResult login(@RequestBody LoginParam loginParam){
            String token = loginService.login(loginParam);
            return new CommonResult(ImmutableMap.of("token",token));
        }
    
        @RequestMapping(value = "/logout",method = RequestMethod.POST)
        private CommonResult logout(HttpServletRequest request){
            loginService.logout(request);
            return new CommonResult();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    Service

    import com.baomidou.mybatisplus.extension.service.IService;
    import com.car.hailing.saas.manage.entity.LoginParam;
    import com.car.hailing.saas.model.entity.TenantUserEntity;
    
    import javax.servlet.http.HttpServletRequest;
    
    public interface LoginService extends IService<TenantUserEntity> {
    
        /**
         * 退出登录
         */
        void logout(HttpServletRequest request);
    
        /**
         * 登录
         * @param loginParam
         * @return
         */
        String login(LoginParam loginParam);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    LoginServiceImpl

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.car.hailing.saas.dao.TenantUserMapper;
    import com.car.hailing.saas.manage.entity.LoginParam;
    import com.car.hailing.saas.manage.entity.LoginTenantUserEntity;
    import com.car.hailing.saas.manage.service.LoginService;
    import com.car.hailing.saas.model.entity.TenantUserEntity;
    import com.car.hailing.saas.utils.JwtUtil;
    import com.car.hailing.saas.utils.redis.RedisUtil;
    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 javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import java.util.Objects;
    
    @Service
    public class LoginServiceImpl extends ServiceImpl<TenantUserMapper, TenantUserEntity> implements LoginService {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Resource
        JwtUtil jwtUtil;
        @Resource
        RedisUtil redisUtil;
    
        @Override
        public void logout(HttpServletRequest request) {
            String token = request.getHeader("token");
            String userId = jwtUtil.getUserId(token);
            redisUtil.hdel("USER_TOKEN",userId+token);
        }
    
        @Override
        public String login(LoginParam loginParam) {
    
            //AuthenticationManager authenticate 进行用户认证
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginParam.getLoginName(),loginParam.getPassWord());
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            //如果认证没通过,给出对应的提示
            if (Objects.isNull(authenticate)){
                throw new RuntimeException("登录失败!");
            }
            LoginTenantUserEntity loginTenantUserEntity = (LoginTenantUserEntity) authenticate.getPrincipal();
            String userId = loginTenantUserEntity.getTenantUserEntity().getId().toString();
            String token = jwtUtil.generateToken(loginTenantUserEntity, userId);
            redisUtil.hset("USER_TOKEN", userId+token, loginTenantUserEntity, 604800L);
            return token;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    TenantUserMapper

    /**
     * 

    * 租户用户表 Mapper 接口 *

    * * @author car-hailing-saas * @since 2022-04-12 */
    @Mapper public interface TenantUserMapper extends BaseMapper<TenantUserEntity> { }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    认证失败的效果
    在这里插入图片描述
    — 自定义权限认证 (如果有特殊的权限验证要求可以自定义权限验证方法)—

    ***例如: ***
    1.在实现了UserDetails接口的实体中定义一个需要验证的权限集合
    LoginTenantUserEntity

        /**
         * 权限集合
         */
        private List<String> permissions;
    
    • 1
    • 2
    • 3
    • 4

    2.在实现了UserDetailsService的实现类中查询出需要校验的权限并添加到实体中(注意修改构造方法)
    UserDetailsServiceImpl

            // TODO 查询对应的权限信息
            List<String> list = new ArrayList<>(Arrays.asList("test","admin"));
            return new LoginTenantUserEntity(tenantUserEntity,list);
    
    • 1
    • 2
    • 3

    3.编写自定义权限校验类

    import com.car.hailing.saas.manage.entity.LoginTenantUserEntity;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    
    /**
     * 自定义权限校验
     * 添加到spring容器中最好自定义一个Bean名称,这样在注解中用到就会比较清晰
     */
    @Component("customize")
    public class CustomizeExpressionRoot {
    
        public boolean customizeHasAuthority(String authority){
            //获取当前用户权限
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            LoginTenantUserEntity loginTenantUserEntity = (LoginTenantUserEntity) authentication.getPrincipal();
            List<String> permissions = loginTenantUserEntity.getPermissions();
            //判断用户群权限集合中是否存在authority
            return permissions.contains(authority);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Coenroller (注解模式验证添加权限验证)

    @RestController
    @RequestMapping("/test")
    public class SecrityController {
    
        @RequestMapping(value = "/index",method = RequestMethod.GET)
        //Secrity 的权限验证方法不需要 加@ 符号 
    //    @PreAuthorize("hasAuthority('test')")
        //自定义的权限验证类 bean名字前需要加 @ 符号  test 是这个接口需要具备的访问权限    
        @PreAuthorize("@customize.customizeHasAuthority('test')")  
        public CommonResult test(){
            return new CommonResult("OK");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注解的方法与配置用的方法是一样的,只是可以使用配置类方式或者是注解方式来配置

    hasAuthority(String authority)  //单个权限校验
    hasAnyAuthority(String... authorities) //多个权限校验
    hasRole(String role) //单个权限校验  这个方法会在传入的权限字符串前默认拼一个 defaultRolePrefix = "ROLE_";
    hasAnyRole(String... roles)  // 多个权限校验  这个方法会在传入的权限字符串前默认拼一个 defaultRolePrefix = "ROLE_";
    isAnonymous() //允许匿名访问 
    permitAll() // 任何人都可以访问 
    denyAll() // 任何人都不允许访问
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    源码:
    在这里插入图片描述

  • 相关阅读:
    WebRTC系列-SDP之编码信息收集
    自洽可分的哈密顿系统的辛算法
    [生物信息]临床研究统计分析成长营14天班
    kafka
    目标检测mAP评价指标计算详解
    【竞赛题目】木块(C语言详解)
    css强制一行,自动裁剪文字,用省略号结尾。
    视频监控与视频编解码技术
    一篇文章教你自动化测试如何解析excel文件?
    解决Tmux提示的size x*x from a smaller client窗口缩放问题
  • 原文地址:https://blog.csdn.net/weixin_44137464/article/details/126019709