zoukankan      html  css  js  c++  java
  • 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)
  • 相关阅读:
    条理清晰的搭建SSH环境之整合Hibernate和Spring
    条理清晰的搭建SSH环境之整合Struts和Spring
    条理清晰的搭建SSH环境之添加所需jar包
    日志工具的使用
    ajax上传文件
    input标签添加上disable属性在移动端字体颜色不兼容的解决办法。
    复制复制复制复制复制复制复制
    animate旋转动画练习,css3形变练习
    canvas练习单个矩形形变
    canvas刮刮卡
  • 原文地址:https://www.cnblogs.com/architectforest/p/14601129.html
Copyright © 2011-2022 走看看