• spring boot:在服务端用redis存储jwt登录后的用户信息(spring boot 2.4.4)


    一,用redis存储用户信息的好处?

    1,避免解析token之后需要查库得到用户的信息

    2,  因为jwt的token可以被反解,所以不直接使用username生成token,而是用一个随机的字符串代替

         避免安全问题

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: https://github.com/liuhongdi/

    说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,演示项目的相关信息

    1,地址:

    https://github.com/liuhongdi/jwtredis

    2,功能说明:演示了用redis来存储jwt登录后的用户信息

    3,项目结构:如图:

    三,配置文件说明

    1,pom.xml

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--security begin-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <!--jjwt begin-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
    
            <!--thymeleaf begin-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <!--fastjson begin-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.73</version>
            </dependency>
    
            <!--redis begin-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>2.11.1</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.11.1</version>
            </dependency>
            <!--redis   end-->
            
            <!--jaxb-->
            <dependency>
                <groupId>javax.xml.bind</groupId>
                <artifactId>jaxb-api</artifactId>
                <version>2.3.0</version>
            </dependency>
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-impl</artifactId>
                <version>2.3.0</version>
            </dependency>
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-core</artifactId>
                <version>2.3.0</version>
            </dependency>
            <dependency>
                <groupId>javax.activation</groupId>
                <artifactId>activation</artifactId>
                <version>1.1.1</version>
            </dependency>
    
            <!--mysql mybatis begin-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.3</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>

    2,application.yml

    spring:
      thymeleaf:
        cache: false
        encoding: UTF-8
        mode: HTML
        prefix: classpath:/templates/
        suffix: .html
    #mysql
      datasource:
        url: jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=false
        username: root
        password: lhddemo
        driver-class-name: com.mysql.cj.jdbc.Driver
    #redis1
      redis1:
        enabled: 1
        host: 127.0.0.1
        port: 6379
        password: lhddemo
        database: 0
        lettuce:
          pool:
            max-active: 32
            max-wait: 300
            max-idle: 16
            min-idle: 8
    #mybatis
    mybatis:
      mapper-locations: classpath:/mapper/*Mapper.xml
      type-aliases-package: com.example.demo.mapper

    3,sql:

    建表:

    CREATE TABLE `sys_user` (
     `userId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
     `userName` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
     `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
     `nickName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵称',
     PRIMARY KEY (`userId`),
     UNIQUE KEY `userName` (`userName`)
    ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'

    测试数据:

    INSERT INTO `sys_user` (`userId`, `userName`, `password`, `nickName`) VALUES
    (1, 'lhd', '$2a$10$yGcOz3cdNI6Ya67tqQueS.raxzTOedGsv5jh2BwtRrI5/K9QEIPGq', '老刘');

    四,java代码说明

    1,controller/HomeController.java

    @Controller
    @RequestMapping("/home")
    public class HomeController {
        //session详情
        @GetMapping("/session")
        @ResponseBody
        public RestResult session() {
            System.out.println("-------begin get session:");
            if ( SessionUtil.getCurrentUser() == null) {
                return RestResult.error(ResponseCode.LOGIN_NEED);
            } else {
                Map<String, String> data = new HashMap<String, String>();
                data.put("username", SessionUtil.getCurrentUser().getUsername());
                data.put("userid", String.valueOf(SessionUtil.getCurrentUser().getUserid()));
                data.put("nickname", SessionUtil.getCurrentUser().getNickname());
                data.put("roles", SessionUtil.getCurrentUser().getAuthorities().toString());
                return RestResult.success(data);
            }
    
        }
        //显示getsession页面
        @GetMapping("/getsession")
        public String get() {
            return "home/getsession";
        }
        //显示login页面
        @GetMapping("/login")
        public String login() {
            return "home/login";
        }
    }

    2,result/RestResult.java

    public class RestResult implements Serializable {
    
        //uuid,用作唯一标识符,供序列化和反序列化时检测是否一致
        private static final long serialVersionUID = 7498483649536881777L;
        //标识代码,0表示成功,非0表示出错
        private Integer code;
        //提示信息,通常供报错时使用
        private String msg;
        //正常返回时返回的数据
        private Object data;
        public RestResult(Integer status, String msg, Object data) {
            this.code = status;
            this.msg = msg;
            this.data = data;
        }
        //返回成功数据
        public static RestResult success(Object data) {
            return new RestResult(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMsg(), data);
        }
        public static RestResult success(Integer code,String msg) {
            return new RestResult(code, msg, null);
        }
        //返回出错数据
        public static RestResult error(ResponseCode code) {
            return new RestResult(code.getCode(), code.getMsg(), null);
        }
    
        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 Object getData() {
            return data;
        }
        public void setData(Object data) {
            this.data = data;
        }
    
    }

    3,jwt/JwtAuthticationFilter.java

    @Component
    public class JwtAuthticationFilter implements Filter {
    
        @Resource
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        @Autowired
        private JwtUserDetailsService userDetailsService;
    
        @Resource
        private UserRedisService userRedisService;
    
        @Resource
        private SysUserService sysUserService;
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("----------------AuthticationFilter init");
        }
        //过滤功能
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            //得到当前的url
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            String path = request.getServletPath();
            if (path.equals("/auth/authenticate")) {
                 System.out.println("auth path:"+path);
                 //得到请求的post参数
                String username = "";
                String password = "";
                try {
                    BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
                    StringBuffer sb=new StringBuffer();
                    String s=null;
                    while((s=br.readLine())!=null){
                        sb.append(s);
                    }
                    JSONObject jsonObject = JSONObject.parseObject(sb.toString());
                    username = jsonObject.getString("username");
                    password = jsonObject.getString("password");
                    //System.out.println("name:"+name+" age:"+age);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println("username:"+username);
                System.out.println("password:"+password);
                String authResult = "";
                try{
                    authResult = authenticate(username,password);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("authResult:"+authResult);
                if ("success".equals(authResult)) {
                    SysUser oneUser = sysUserService.getOneUserByUsername(username);//数据库查询 看用户是否存在
                    final UserDetails userDetails = userDetailsService.loadUserBySysUser(oneUser);
                    String origToken = jwtTokenUtil.makeTokenForSave(userDetails.getUsername());
                    final String token = jwtTokenUtil.generateTokenByOrig(origToken);
    
                    //保存到redis
                    oneUser.setOrigToken(origToken);
                    oneUser.setPassword("");
                    //System.out.println("保存到redis的token:"+origToken);
                    userRedisService.setOneUser(oneUser,origToken);
                    //return result
                    Map<String, String> mapData = new HashMap<String, String>();
                    mapData.put("token", token);
                    ServletUtil.printRestResult(RestResult.success(mapData));
                } else if ("badcredential".equals(authResult)){
                    ServletUtil.printRestResult(RestResult.error(ResponseCode.LOGIN_FAIL));
                } else {
                    ServletUtil.printRestResult(RestResult.error(ResponseCode.ERROR));
                }
                return;
            } else {
                System.out.println("not auth path:"+path);
                filterChain.doFilter(servletRequest, servletResponse);
            }
        }
    
        @Override
        public void destroy() {
            System.out.println("----------------filter destroy");
        }
    
        private String authenticate(String username, String password) throws Exception {
            try {
                System.out.println("username:"+username);
                System.out.println("password:"+password);
                authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
                System.out.println("authenticate:will return success");
                return "success";
            } catch (DisabledException e) {
                throw new Exception("USER_DISABLED", e);
            } catch (BadCredentialsException e) {
                System.out.println("BadCredentialsException");
                System.out.println(e.toString());
                //throw new Exception("INVALID_CREDENTIALS", e);
                return "badcredential";
            }
        }
    }

    4,jwt/JwtRequestFilter.java

    @Component
    public class JwtRequestFilter extends OncePerRequestFilter {
    
        @Autowired
        private JwtUserDetailsService jwtUserDetailsService;
    
        @Resource
        private UserRedisService userRedisService;
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws ServletException, IOException {
            final String requestTokenHeader = request.getHeader("Authorization");
            String username = null;
            String jwtToken = null;
            // JWT Token 获取请求头部的 Bearer
            System.out.println("filter:header:"+requestTokenHeader);
            // only the Token
            if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
                //System.out.println("filter :requestTokenHeader not null and start with bearer");
                jwtToken = requestTokenHeader.substring(7);
                try {
                    username = jwtTokenUtil.getUsernameFromToken(jwtToken);
                } catch (IllegalArgumentException e) {
                    System.out.println("Unable to get JWT Token");
                } catch (ExpiredJwtException e) {
                    System.out.println("JWT Token has expired");
                } catch (MalformedJwtException e) {
                    System.out.println("JWT Token MalformedJwtException");
                }
            } else {
                //System.out.println("filter :requestTokenHeader is null || not start with bearer");
                //logger.warn("JWT Token does not begin with Bearer String");
            }
    
            // 验证,
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                System.out.println("-----------get username from client:"+username);
                SysUser oneUser = userRedisService.getOneUserByUserToken(username);
                if (oneUser == null) {
                    ServletUtil.printRestResult(RestResult.error(ResponseCode.LOGIN_NEED));
                    return;
                }
                //get UserDetails
                UserDetails userDetails = this.jwtUserDetailsService.loadUserBySysUser(oneUser);
    
                // JWT 验证通过 使用Spring Security 管理
                if (jwtTokenUtil.validateTokenByOrigToken(jwtToken, oneUser.getOrigToken())) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                } else {
                   System.out.println("jwtTokenUtil.validateToken not success");
                }
            }
            chain.doFilter(request, response);
        }
    }

    5,jwt/JwtTokenUtil.java

    @Component
    public class JwtTokenUtil implements Serializable {
        private static final long serialVersionUID = -2550185165626007488L;
        public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
    
        private String secret = "liuhongdi";
        //retrieve username from jwt token
        public String getUsernameFromToken(String token) {
            return getClaimFromToken(token, Claims::getSubject);
        }
        //retrieve expiration date from jwt token
        public Date getExpirationDateFromToken(String token) {
            return getClaimFromToken(token, Claims::getExpiration);
        }
    
        public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
            final Claims claims = getAllClaimsFromToken(token);
            return claimsResolver.apply(claims);
        }
        //for retrieveing any information from token we will need the secret key
        private Claims getAllClaimsFromToken(String token) {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
        //check if the token has expired
        private Boolean isTokenExpired(String token) {
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }
        //make a save token
        public String makeTokenForSave(String userName) {
            //得到当前时间
            long time = System.nanoTime();
            //得到随机数
            Random ran = new Random();
            int x = ran.nextInt(9000) + 1000;
            //md5后返回
            String base = time+"_"+userName+"_"+x;
            String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
            return md5;
        }
    
        //generate token for user
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>();
            //String token = makeTokenForSave(userDetails.getUsername());
            return doGenerateToken(claims, userDetails.getUsername());
        }
    
        //generate token for user
        public String generateTokenByOrig(String origToken) {
            Map<String, Object> claims = new HashMap<>();
            return doGenerateToken(claims, origToken);
        }
    
        //generate token
        private String doGenerateToken(Map<String, Object> claims, String subject) {
            return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                    .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                    .signWith(SignatureAlgorithm.HS512, secret).compact();
        }
        //validate token
        public Boolean validateToken(String token, UserDetails userDetails) {
            final String username = getUsernameFromToken(token);
            return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
        }
    
        //validate token by orig
        public Boolean validateTokenByOrigToken(String token, String origToken) {
            final String username = getUsernameFromToken(token);
            //System.out.println("username for valid:"+username);
            return (username.equals(origToken) && !isTokenExpired(token));
        }
    }

    6,jwt/JwtUserDetailsService.java

    @Service
    public class JwtUserDetailsService implements UserDetailsService {
        @Resource
        private SysUserService sysUserService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            System.out.println("-----loadUserByUsername");
            SysUser oneUser = sysUserService.getOneUserByUsername(username);//数据库查询 看用户是否存在
            String encodedPassword = oneUser.getPassword();
            Collection<GrantedAuthority> collection = new ArrayList<>();//权限集合
            //用户角色role前面要添加ROLE_
            List<String> roles = oneUser.getRoles();
            System.out.println(roles);
            for (String roleone : roles) {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+roleone);
                collection.add(grantedAuthority);
            }
            //给用户增加用户id和昵称
            SecUser user = new SecUser(username,encodedPassword,collection);
            user.setUserid(oneUser.getUserId());
            user.setNickname(oneUser.getNickName());
            return user;
        }
    
        public UserDetails loadUserBySysUser(SysUser oneUser) throws UsernameNotFoundException {
            System.out.println("-----loadUserByUser");
            //SysUser oneUser = sysUserService.getOneUserByUsername(username);//数据库查询 看用户是否存在
            String encodedPassword = oneUser.getPassword();
            Collection<GrantedAuthority> collection = new ArrayList<>();//权限集合
            //用户角色role前面要添加ROLE_
            List<String> roles = oneUser.getRoles();
            System.out.println(roles);
            for (String roleone : roles) {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+roleone);
                collection.add(grantedAuthority);
            }
            //给用户增加用户id和昵称
            SecUser user = new SecUser(oneUser.getUserName(),encodedPassword,collection);
            user.setUserid(oneUser.getUserId());
            user.setNickname(oneUser.getNickName());
            return user;
        }
    }

    7,impl/UserRedisServiceImpl.java

    @Service
    public class UserRedisServiceImpl implements UserRedisService {
    
        @Resource
        private RedisTemplate redis1Template;
    
        //从redis查询查询得到用户信息
        @Override
        public SysUser getOneUserByUserToken(String userToken){
            System.out.println("从redis查询得到用户信息");
            SysUser userOne;
            Object usersr = redis1Template.opsForValue().get("jwt_"+userToken);
            if (usersr == null) {
                userOne = null;
            } else {
                if (usersr.equals("-1")) {
                    userOne = null;
                } else {
                    userOne = (SysUser)usersr;
                }
            }
            return userOne;
        }
    
        //向redis写入用户信息,保存时长是jwt的配置
        @Override
        public void setOneUser(SysUser user,String userToken){
            long timeLenghth = JwtTokenUtil.JWT_TOKEN_VALIDITY;
            redis1Template.opsForValue().set("jwt_"+userToken,user,timeLenghth, TimeUnit.SECONDS);
        }
    }

    8,impl/SysUserServiceImpl.java

    @Service
    public class SysUserServiceImpl implements SysUserService {
        @Resource
        private UserMapper userMapper;
        //根据用户名查询数据库得到用户信息
        @Override
        public SysUser getOneUserByUsername(String username) {
            System.out.println("从数据库查询得到用户信息");
            SysUser user_one = userMapper.selectOneUserByUserName(username);
            return user_one;
        }
    
    }

    9,其他相关代码可访问github

    五,测试效果

    1,访问login

    http://127.0.0.1:8080/home/login

    登录:

     2,访问session:

    http://127.0.0.1:8080/home/getsession

    点击 get session info按钮:

     3,从redis进行查询:

    [root@localhost liuhongdi]# /usr/local/soft/redis/bin/redis-cli
    127.0.0.1:6379> get jwt_73179e0988afc51896f7810df9716e52
    "{"@class":"com.jwtredis.demo.pojo.SysUser","userId":1,"userName":"lhd","password":"",
    "roles":["java.util.ArrayList",[]],"nickName":"xe8x80x81xe5x88x98",
    "origToken":"73179e0988afc51896f7810df9716e52"}"

    六,查看spring boot的版本

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v2.4.4)
  • 相关阅读:
    jQuery EasyUI API 中文文档 数字框(NumberBox)
    jQuery EasyUI API 中文文档 数值微调器(NumberSpinner)
    jQuery EasyUI API 中文文档 日期时间框(DateTimeBox)
    jQuery EasyUI API 中文文档 微调器(Spinner)
    jQuery EasyUI API 中文文档 树表格(TreeGrid)
    jQuery EasyUI API 中文文档 树(Tree)
    jQuery EasyUI API 中文文档 属性表格(PropertyGrid)
    EntityFramework 数据操作
    jQuery EasyUI API 中文文档 对话框(Dialog)
    jQuery EasyUI API 中文文档 组合表格(ComboGrid)
  • 原文地址:https://www.cnblogs.com/architectforest/p/14601129.html
Copyright © 2020-2023  润新知