IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 前后端分离使用SpringSecurity整合JWT返回JSON -> 正文阅读

[Java知识库]前后端分离使用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;
  }
 // 这一坨是加载依赖...
    
    
    
  /**
   * 配置SpringSecurity的登陆详细类
   * @param auth
   * @throws Exception
   */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(this.myUserDetailsServiceImpl).passwordEncoder(new BCryptPasswordEncoder());
  }

  /**
   * SpringSecurity普通配置
   * @param http
   * @throws Exception
   */
  @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<MyUser> myUserQueryWrapper = new QueryWrapper<>();
    myUserQueryWrapper.eq("account", username);
    MyUser myUsers = userMapper.selectOne(myUserQueryWrapper); // vo类

    List<GrantedAuthority> 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<String, Object> 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<String, Object> claims = new HashMap<>();
    claims.put("username", authentication.getName());

    List<String> 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<String, Object> 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<String, Object> 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<String> grantedAuthority = (ArrayList<String>)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<String, Object> 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<String, Object> outSecurity() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("msg", "out OK");
    return map;
  }

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

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

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

  • 登陆测试:

在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64rrIrqH-1633254521510)(/前后端分离使用SpringSecurity整合JWT返回JSON2.png)]

  • 认证测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UGaL35ca-1633254521513)(/前后端分离使用SpringSecurity整合JWT返回JSON3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rkeWYgZV-1633254521515)(/前后端分离使用SpringSecurity整合JWT返回JSON4.png)]

  • 权限测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X6hxX4bR-1633254521516)(/前后端分离使用SpringSecurity整合JWT返回JSON5.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bb1A3LdQ-1633254521517)(/前后端分离使用SpringSecurity整合JWT返回JSON6.png)]

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

源码

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-06 12:04:30  更:2021-10-06 12:05:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:55:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码