首页 > 编程学习 > Security提高-集成JWT

Security提高-集成JWT

发布时间:2022/8/23 11:34:37

注意:请先看《springboot从0开始搭建rbac的security权限认证》再看本篇

1.引入jwt依赖

<!-- JWT -->
       <dependency>
           <groupId>io.jsonwebtoken</groupId>
           <artifactId>jjwt</artifactId>
           <version>0.9.1</version>
       </dependency>

2.在application.properties添加JWT的配置

# JWT??
jwt:
 # JWT??????
tokenHeader: Authorization
 # JWT ???????
secret: wrs-secret
 # JWT ?????(60*60*24)
expiration: 604800
 # JWT???????
tokenHead: Bearer
spring:
datasource:
  driver-class-name: com.mysql.cj.jdbc.Driver
  url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&autoReconnect=true
  username: root
  password: 123456
server:
port: 9999

3.书写JWT工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtTokenUtil {
   // 用户名的key
   private static final String CLAIM_KEY_USERNAME = "sub";
   // jwt创建时间
   private static final String CLAIM_KEY_CREATED = "created";

   /**
    * 去application.yml拿jwt密钥和jwt失效时间
    */
   @Value("${jwt.secret}")
   private String secret;
   @Value("${jwt.expiration}")
   private Long expiration;

   /**
    * 根据用户信息生成Token
    *
    * @param userDetails
    * @return
    */
   public String generateToken(UserDetails userDetails) {
       HashMap<String, Object> claims = new HashMap<>();
       claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
       claims.put(CLAIM_KEY_CREATED, new Date());
       return generateToken(claims);
  }

   /**
    * 从Token中获取username
    * @param token
    * @return
    */
   public String getUsernameFromToken(String token) {
       String username;
       try {
           Claims claims = getClaimsFromToken(token);
           username = claims.getSubject();
      } catch (Exception e) {
           username = null;
      }
       return username;
  }

   /**
    * 从Token中获取荷载
    * @param token
    * @return
    */
   private Claims getClaimsFromToken(String token) {
       Claims claims = null;
       try {
           claims = Jwts.parser()
                  .setSigningKey(secret)
                  .parseClaimsJws(token)
                  .getBody();
      } catch (Exception e) {
           e.printStackTrace();
      }
       return claims;
  }

   /**
    * 验证Token是否有效
    * @param token
    * @param userDetails
    * @return
    */
   public boolean validateToken(String token, UserDetails userDetails) {
       String username = getUsernameFromToken(token);
       return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
  }

   /**
    * 判断Token是否失效
    * @param token
    * @return
    */
   private boolean isTokenExpired(String token) {
       Date expireDate = getExpiredDateFromToken(token);
       return expireDate.before(new Date());
  }

   /**
    * 从Token中获取过期时间
    * @param token
    * @return
    */
   private Date getExpiredDateFromToken(String token) {
       Claims claims = getClaimsFromToken(token);
       return claims.getExpiration();
  }

   /**
    * 根据荷载生成JWT Token
    *
    * @param claims
    * @return
    */
   private String generateToken(Map<String, Object> claims) {
       return Jwts.builder()
              .setClaims(claims)
              .setExpiration(generateExpirationDate())
              .signWith(SignatureAlgorithm.HS512, secret)
              .compact();
  }

   /**
    * 生成Token失效时间
    *
    * @return
    */
   private Date generateExpirationDate() {
       return new Date(System.currentTimeMillis() + expiration * 1000);
  }
}

4.书写JWT的过滤器

import com.zhuoyue.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

   @Value("${jwt.tokenHeader}")
   private String tokenHeader;
   @Value("${jwt.tokenHead}")
   private String tokenHead;
   @Resource
   private JwtTokenUtil jwtTokenUtil;
   @Resource
   private UserDetailsService userDetailsService;
   @Override
   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
           throws ServletException, IOException, NumberFormatException {
       // 获取Header
       String authHeader = request.getHeader(tokenHeader);
       // 存在token但不是tokenHead开头
       if (null != authHeader && authHeader.startsWith(tokenHead)) {
           // 字段截取authToken
           String authToken = authHeader.substring(tokenHead.length());
           // 根据authToken获取username
           String username = jwtTokenUtil.getUsernameFromToken(authToken);
           // token存在用户名但未登录
           if (null != username && null == SecurityContextHolder.getContext().getAuthentication()) {
               // 登录
               UserDetails userDetails = userDetailsService.loadUserByUsername(username);
               // 验证token是否有效,如果有效,将他重新放到用户对象里。
               if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                   UsernamePasswordAuthenticationToken authenticationToken =
                           new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                   // 重新设置到用户对象里
                   authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                   SecurityContextHolder.getContext().setAuthentication(authenticationToken);
              }
          }
      }
       // 放行
       chain.doFilter(request, response);
  }
}

5.书写未登录拦截

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhuoyue.common.RespBean;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {
   @Override
   public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
           throws IOException, ServletException {
       // 通过response设置编码格式
       response.setCharacterEncoding("UTF-8");
       // 设置ContentType
       response.setContentType("application/json");
       // 输出流
       PrintWriter out = response.getWriter();
       RespBean bean = RespBean.error("未登录,请登录!");
       bean.setCode(401);
       out.write(new ObjectMapper().writeValueAsString(bean));
       out.flush();
       out.close();
  }
}

6.书写权限不足拦截

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhuoyue.common.RespBean;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
   @Override
   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
           throws IOException, ServletException {
       // 通过response设置编码格式
       response.setCharacterEncoding("UTF-8");
       // 设置ContentType
       response.setContentType("application/json");
       // 输出流
       RespBean bean = RespBean.error("权限不足,请联系管理员!");
       PrintWriter out = response.getWriter();
       bean.setCode(403);
       out.write(new ObjectMapper().writeValueAsString(bean));
       out.flush();
       out.close();
  }
}

7.修改UserService添加login接口

import com.zhuoyue.common.RespBean;
import org.springframework.security.core.userdetails.UserDetailsService;

public interface UserService extends UserDetailsService {

   RespBean login(String username,String password);
}

8.书写Uservice的实现类UserServiceImpl

import com.zhuoyue.common.RespBean;
import com.zhuoyue.mapper.PerssionMapper;
import com.zhuoyue.mapper.UserMapper;
import com.zhuoyue.po.PerssionPO;
import com.zhuoyue.po.UserPO;
import com.zhuoyue.servcie.UserService;
import com.zhuoyue.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {
   @Resource
   private PasswordEncoder passwordEncoder;
   @Value("${jwt.tokenHead}")
   private String tokenHead;
   @Resource
   private UserMapper userMapper;
   @Resource
   private JwtTokenUtil jwtTokenUtil;

   @Override
   public RespBean login(String username, String password) {
       UserDetails userDetails = loadUserByUsername(username);
       if (userDetails == null)return RespBean.error("登录失败!");
       if (passwordEncoder.matches(password,userDetails.getPassword())){
           if (userDetails.isEnabled()){
               UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
               SecurityContextHolder.getContext().setAuthentication(authenticationToken);
               String token = jwtTokenUtil.generateToken(userDetails);
               HashMap<Object, Object> map = new HashMap<>();
               map.put("token",token);
               map.put("tokenHead",tokenHead);
               return RespBean.success("登录成功!",map);
          }
           return RespBean.error("账号已被禁用请联系管理员");
      }
       return RespBean.error("用户名或密码错误!");
  }

   @Override
   public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
       UserPO userPO = userMapper.findUserByUserName(s);
       if (userPO==null)return null;
       List<PerssionPO> all = userMapper.findPerssionByUserName(s);
       List<GrantedAuthority> collect = all.stream().map(perssionPO -> new SimpleGrantedAuthority(perssionPO.getCode())).collect(Collectors.toList());
       userPO.setAuthorities(collect);
       return userPO;
  }
}

9.修改security的配置类

import com.zhuoyue.mapper.PerssionMapper;
import com.zhuoyue.po.PerssionPO;
import com.zhuoyue.servcie.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
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.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;
import java.util.List;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   @Resource
   private RestAuthorizationEntryPoint restAuthorizationEntryPoint;
   @Resource
   private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
   @Resource
   private UserService userService;
   @Resource
   private PerssionMapper perssionMapper;

   @Bean
   public PasswordEncoder passwordEncoder() {
       return new BCryptPasswordEncoder();
  }
   @Bean
   public JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter() {
       return new JwtAuthorizationTokenFilter();
  }

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
  }
   /**
    * 放行路径
    * @param web
    * @throws Exception
    */
   @Override
   public void configure(WebSecurity web) throws Exception {
       web.ignoring().antMatchers(
               "/captcha",
               "/login",
               "/logout",
               "/css/**",
               "/js/**",
               "/index.html",
               "favicon.ico",
               "/doc.html",
               "/webjars/**",
               "/swagger-resources/**",
               "/v2/api-docs/**"
              );
  }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       // 使用JWT,不需要csrf
       http.csrf().disable()
               // 使用JWT,不需要session
              .sessionManagement()
              .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              .and()
               // 禁用缓存
              .headers()
              .cacheControl();
       ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests = http.authorizeRequests();
       List<PerssionPO> mapperAll = perssionMapper.findAll();
       mapperAll.forEach(perssionPO ->
               requests.antMatchers(perssionPO.getUrl()).hasAuthority(perssionPO.getCode()));
       //requests.anyRequest().denyAll().and().headers().cacheControl();
       requests.anyRequest().permitAll();
       // 添加JWT 登录授权过滤器
       http.addFilterBefore(jwtAuthorizationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
       // 添加自定义未授权和未登录结果返回
       http.exceptionHandling()
              .accessDeniedHandler(restfulAccessDeniedHandler)
              .authenticationEntryPoint(restAuthorizationEntryPoint);
  }


}

10.书写测试Controller

import com.zhuoyue.common.RespBean;
import com.zhuoyue.mapper.UserMapper;
import com.zhuoyue.po.UserPO;
import com.zhuoyue.servcie.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.security.Principal;

@RestController
public class TestController {
   @Resource
   private UserService userService;
   @Resource
   private UserMapper userMapper;
   @PostMapping("/login")
   public RespBean login(String username,String password){
       return userService.login(username,password);
  }
   @PostMapping("/r/r")
   public String r(){
       return "我是r,我不要权限";
  }
   @PostMapping("/r/r1")
   public String r1(){
       return "我是r1,我需要p1权限";
  }
   @PostMapping("/r/r2")
   public String r2(){
       return "我是r2,我需要p3权限";
  }
   @PostMapping("/r/r3")
   public String r3(){
       return "我是r3,我不需要权限";
  }

   @GetMapping("/r/info")
   public UserPO getAdminInfo(Principal principal) {
       if (principal != null) {
           String username = principal.getName();
           UserPO user = userMapper.findUserByUserName(username);
           // 将用户名密码设置null,安全性。
           user.setPassword(null);
           return user;
      }
       return null;
  }
}
 
Copyright © 2010-2022 mfbz.cn 版权所有 |关于我们| 联系方式|豫ICP备15888888号