跳转至

JWT身份验证

使用JWT验证信息代替Session验证信息
Session不能跨域,使用JWT代替解决跨域请求,也能实现前后端完全分离缓解服务器压力

Springboot Maven依赖安装

  • Maven仓库地址
  • 需要安装的Java-jwt模块, 在pom.xml中添加依赖
    • jjwt-api
    • jjwt-impl
    • jjwt-jackson
    • spring-boot-starter-security

修改Spring Security

  • 实现service.impl.UserDetailsServiceImpl类,继承自UserDetailsService接口,用来接入数据库信息

    package com.kob.backend.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.kob.backend.mapper.UserMapper;
    import com.kob.backend.pojo.User;
    
    import com.kob.backend.service.impl.utils.UserDetailsImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("username", username);
            User user = userMapper.selectOne(queryWrapper);
    
            if(user == null) {
                throw new RuntimeException("用户不存在");
            }
    
            return new UserDetailsImpl(user);
        }
    }
    

  • 实现config.SecurityConfig类,用来实现用户密码的加密存储

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

  • 实现service.impl.utils.UserDetailsImpl类,继承自UserDetails接口,用来修改用户权限

    package com.kob.backend.service.impl.utils;
    
    import com.kob.backend.pojo.User;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class UserDetailsImpl implements UserDetails {
    
        private User user;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @Override
        public String getUsername() {
            return user.getUsername();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    

Jwt配置

  1. 实现utils.JwtUtil类,为jwt工具类,用来创建、解析jwt token

    package com.kob.backend.utils;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    // import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.stereotype.Component;
    
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Base64;
    import java.util.Date;
    import java.util.UUID;
    
    @Component
    public class JwtUtil {
        public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
        public static final String JWT_KEY = "asdnHSLK131657asd516AKFH1654sdasd98LKJASDasijasifj1564";
    
        public static String getUUID() {
            return UUID.randomUUID().toString().replaceAll("-", "");
        }
    
        public static String createJWT(String subject) {
            JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
            return builder.compact();
        }
    
        private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
            // SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            SecretKey secretKey = generalKey();
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
            if (ttlMillis == null) {
                ttlMillis = JwtUtil.JWT_TTL;
            }
    
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            return Jwts.builder()
                    .id(uuid)
                    .subject(subject)
                    .issuer("sg")
                    .issuedAt(now)
                    .signWith(secretKey)
                    .expiration(expDate);
        }
    
        public static SecretKey generalKey() {
            byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
            return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
        }
    
        public static Claims parseJWT(String jwt) throws Exception {
            SecretKey secretKey = generalKey();
            return Jwts.parser()
                    .verifyWith(secretKey)
                    .build()
                    .parseSignedClaims(jwt)
                    .getPayload();
        }
    }
    

  2. 实现config.filter.JwtAuthenticationTokenFilter类,用来验证jwt token,如果验证成功,则将User信息注入上下文中

    package com.kob.backend.config.filter;
    
    import com.kob.backend.mapper.UserMapper;
    import com.kob.backend.pojo.User;
    import com.kob.backend.service.impl.utils.UserDetailsImpl;
    import com.kob.backend.utils.JwtUtil;
    import io.jsonwebtoken.Claims;
    import org.jetbrains.annotations.NotNull;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
            String token = request.getHeader("Authorization");
    
            if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
                filterChain.doFilter(request, response);
                return;
            }
    
            token = token.substring(7);
    
            String userid;
            try {
                Claims claims = JwtUtil.parseJWT(token);
                userid = claims.getSubject();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
    
            User user = userMapper.selectById(Integer.parseInt(userid));
    
            if (user == null) {
                throw new RuntimeException("用户名未登录");
            }
    
            UserDetailsImpl loginUser = new UserDetailsImpl(user);
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(loginUser, null, null);
    
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    
            filterChain.doFilter(request, response);
        }
    }
    

  3. 配置config.SecurityConfig类,放行登录、注册等接口

    package com.kob.backend.config;
    
    import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    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.SecurityFilterChain;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public AuthenticationManager authenticationManagerBean(
                AuthenticationConfiguration authenticationConfiguration
        ) throws Exception {
            return authenticationConfiguration.getAuthenticationManager();
        }
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 放行https链接, 其余拦截
                    .antMatchers("/user/account/token/", "/user/account/register/").permitAll() //公开
                    .antMatchers(HttpMethod.OPTIONS).permitAll()
                    // .antMatchers("/pk/start/game/", "/pk/receive/bot/move/").hasIpAddress("127.0.0.1") // 指定Ip放行
                    .anyRequest().authenticated();
    
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
            return http.build();
        }
    
        // 放行ws链接, 其余拦截
        @Bean
        public WebSecurityCustomizer webSecurityCustomizer() {
            return (web) -> web.ignoring().antMatchers(
                    "/websocket/**",
                    "/css/**",
                    "/js/**",
                    "/img/**");
        }
    }