栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

前后端分离使用SpringSecurity整合JWT返回JSON

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

前后端分离使用SpringSecurity整合JWT返回JSON

前后端分离使用SpringSecurity整合JWT返回JSON
  • 环境:springboot 2.6.0-SNAPSHOT

参考大佬博客:https://blog.csdn.net/mengxianglong123/article/details/112463172

  • 本文没有什么是JWT,什么是SpringSecurity不了解的自行学习,且本文不涉及OAuth2
配置
  • 案例属于前后端分离,且后端连接DB

application.yaml

server:
  port: 9090

spring:
  # jdbc
  datasource:
    username: root
    password: 990415
    url: jdbc:mysql://127.0.0.1:3306/my_blog?serverTimezone=UTC&characterEncoding=utf-8&useUnicode=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource

mybatis-plus:
  # pojo
  type-aliases-package: top.jybill.demo.pojo
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      table-prefix: sm_

使用mybatis-plus便于开发

SpringSecurityConfig配置类

@Configuration
@EnableWebSecurity
// 开启注解的使用
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@ConditionalOnClass({
        MyUserDetailsServiceImpl.class, // 登陆处理器
        MyAccessDeniedHandler.class, // 登陆后出现权限异常的处理器
        MyAuthenticationExceptionHandler.class, // 登录前未承认出现异常的处理器
        MyAuthenticationSuccessHandler.class, // 登录成功处理器 (生成token)
        MyForwardAuthenticationFailureHandler.class, // 登录失败处理器
        MyJWTAuthenticationFilter.class // JWT校验的一个过滤器, 最先进行校验的过滤器 (校验token)
})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

 // 这一坨是加载依赖...
  private final UserDetailsService myUserDetailsServiceImpl;
  private final MyAccessDeniedHandler myAccessDeniedHandler;
  private final MyAuthenticationExceptionHandler myAuthenticationExceptionHandler;
  private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
  private final MyForwardAuthenticationFailureHandler myForwardAuthenticationFailureHandler;
  private final MyJWTAuthenticationFilter myJWTAuthenticationFilter;

  @Autowired
  public SpringSecurityConfig(UserDetailsService myUserDetailsServiceImpl,
                              MyAccessDeniedHandler myAccessDeniedHandler,
                              MyAuthenticationExceptionHandler myAuthenticationExceptionHandler,
                              MyAuthenticationSuccessHandler myAuthenticationSuccessHandler,
                              MyForwardAuthenticationFailureHandler myForwardAuthenticationFailureHandler,MyJWTAuthenticationFilter myJWTAuthenticationFilter) {
    this.myUserDetailsServiceImpl = myUserDetailsServiceImpl;
    this.myAccessDeniedHandler = myAccessDeniedHandler;
    this.myAuthenticationExceptionHandler = myAuthenticationExceptionHandler;
    this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler;
    this.myForwardAuthenticationFailureHandler = myForwardAuthenticationFailureHandler;
    this.myJWTAuthenticationFilter = myJWTAuthenticationFilter;
  }
 // 这一坨是加载依赖...
    
    
    
  
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(this.myUserDetailsServiceImpl).passwordEncoder(new BCryptPasswordEncoder());
  }

  
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/login/**", "/logout/**").permitAll()
            .antMatchers("/t/in").authenticated()
            .antMatchers("/t/inRole").authenticated()
            .antMatchers("/t/out").permitAll()

            .and()

            .formLogin()
            .successHandler(this.myAuthenticationSuccessHandler)
            .failureHandler(this.myForwardAuthenticationFailureHandler)

            .and()

            .exceptionHandling().accessDeniedHandler(this.myAccessDeniedHandler) // 异常处理
            .authenticationEntryPoint(this.myAuthenticationExceptionHandler)
            .and()

          .csrf().disable() // 关闭csrf跨域攻击

            // 前后端分离JWT 关闭session
          .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

          // 前后端分离JWT过滤器
          .and().addFilterBefore(this.myJWTAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

          .headers().cacheControl()// 关闭页面缓存

    ;
  }

  // 密码编码
  @Bean
  public PasswordEncoder getPasswordEncoder() {
    return new BCryptPasswordEncoder();
  }

  // 授权管理器
  @Bean
  public AuthenticationManager getAuthenticationManager() throws Exception {
    return super.authenticationManagerBean();
  }
}

自定义service登陆类

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {

  private final UserMapper userMapper; // 使用的mybatis-plus

  @Autowired
  public MyUserDetailsServiceImpl(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

      // mybatis-plus操作
    QueryWrapper myUserQueryWrapper = new QueryWrapper<>();
    myUserQueryWrapper.eq("account", username);
    MyUser myUsers = userMapper.selectOne(myUserQueryWrapper); // vo类

    List grantedAuthorities = null;
    if (myUsers.getAdmin()) {
        // springsecurity将字符转权限的方法
      grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); 
    }

      // 返回查出来的用户, SpringSecurity定义的user类
    return new User(myUsers.getAccount(),
            new BCryptPasswordEncoder().encode(myUsers.getPassword()),
            "1".equals(myUsers.getStatus()),
            "1".equals(myUsers.getStatus()),
            "1".equals(myUsers.getStatus()),
            "1".equals(myUsers.getStatus()),
            grantedAuthorities
            );
  }

}

登陆前异常处理类:没有登陆想访问需要登陆的接口

@Component
public class MyAuthenticationExceptionHandler implements AuthenticationEntryPoint {
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);

    HashMap ret = new HashMap<>();
    // 未登录
    if (e instanceof InsufficientAuthenticationException) {
      ret.put("code", HttpServletResponse.SC_FORBIDDEN);
      ret.put("msg", e.getMessage());
      response.getWriter().write(JSON.toJSONString(ret));
    }
  }
}

登陆成功处理类:登陆成功生成token并生成springsecurity的安全上下文

@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    HashMap claims = new HashMap<>();
    claims.put("username", authentication.getName());

    List role = new ArrayList<>();
    for (GrantedAuthority g : authentication.getAuthorities()) {
      role.add(g.getAuthority());
    }
    claims.put("grantedAuthority", role);
    String token = JJWTUtil.createToken(claims);

    //将生成的authentication放入容器中,生成安全的上下文
    SecurityContextHolder.getContext().setAuthentication(authentication);

    HashMap ret = new HashMap<>();
    ret.put("token", token);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.getWriter().println(JSON.toJSONString(ret));
  }
}

我们后续需要拿着返回的JSON - token访问接口

登陆失败处理类:账号密码错误

@Component
public class MyForwardAuthenticationFailureHandler implements AuthenticationFailureHandler{
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8");
    HashMap ret = new HashMap<>();
    ret.put("code", 400);
    ret.put("msg", "账号密码错误");
    response.getWriter().println(JSON.toJSONString(ret));
  }
}

JWT校验过滤器:校验token是否合法,是否过期

@Slf4j
@Component
public class MyJWTAuthenticationFilter extends OncePerRequestFilter {

    // request headers 的key
  private static final String TOKEN_PREFIX = "Authentication";

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    String authentication = request.getHeader(TOKEN_PREFIX);
    Claims claims = null;

    // 校验JWT
    if (StrUtil.isNotBlank(authentication)) {
      claims = JJWTUtil.checkToken(authentication);
    } else {
      logger.warn("没有传JWT");
    }

    // once we get the token validate it.
    log.info("security上下文: {}", SecurityContextHolder.getContext().getAuthentication());
    if (claims != null && SecurityContextHolder.getContext().getAuthentication() == null) {
      String username = claims.get("username", String.class);
      ArrayList grantedAuthority = (ArrayList)claims.get("grantedAuthority");
      StringBuilder authorityString = new StringBuilder();
      for (String s : grantedAuthority) {
        authorityString.append(s);
        authorityString.append(",");
      }
      String s = StrUtil.subBefore(authorityString.toString(), ",", true);
      UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
              new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(s));
      usernamePasswordAuthenticationToken
              .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); // 生成安全上下文
    }
    chain.doFilter(request, response);
  }
}

token合法、为失效会生成安全上下文,否则交给springsecurity自己判断,肯定是失败的,就会走登陆后权限异常的处理器

权限异常处理器

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    HashMap map = new HashMap<>();
    map.put("msg", accessDeniedException.getMessage());
    map.put("code", 403);
    System.out.println(map);
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8");
    response.getWriter().println(JSON.toJSONString(map));
  }
}
controller + postman测试
@RestController
@RequestMapping("/t")
public class TestController {

  @GetMapping("out")
  public Map outSecurity() {
    HashMap map = new HashMap<>();
    map.put("msg", "out OK");
    return map;
  }

  @GetMapping("in")
  public Map inSecurity() {
    HashMap map = new HashMap<>();
    map.put("msg", "in OK");
    return map;
  }

  @GetMapping("inAutority")
  @PreAuthorize("hasAuthority('ban')")
  public Map inAutority(Authentication authentication) {
    HashMap map = new HashMap<>();
    map.put("msg", "ban");
    map.put("auth", authentication);
    return map;
  }

  @GetMapping("notInAutority")
  @PreAuthorize("hasAuthority('admin')")
  public Map notInAutority(Authentication authentication) {
    HashMap map = new HashMap<>();
    map.put("msg", "admin");
    map.put("auth", authentication);
    return map;
  }
}

  • 登陆测试:


  • 认证测试:

  • 权限测试:

jwt本身的特性就是无状态,所以没必要后端在db保存

源码

码云:https://gitee.com/JYbill/springseucirty-jjwt

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/287186.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号