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

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

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

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

     避免安全问题

 

说明:刘宏缔的架构森林是一个专注架构的博客,

网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/27/spring-boot-zai-fu-wu-duan-yong-redis-cun-chu-jwt-deng-lu/

         对应的源码可以访问这里获取: 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\":\"\xe8\x80\x81\xe5\x88\x98\",
\"origToken\":\"73179e0988afc51896f7810df9716e52\"}"

 

六,查看spring boot的版本

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

 

posted @ 2021-03-31 13:52  刘宏缔的架构森林  阅读(3607)  评论(0编辑  收藏  举报