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类,用来实现用户密码的加密存储
-
实现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配置¶
-
实现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(); } }
-
实现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); } }
-
配置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/**"); } }