SpringBoot-整合Security和JWT(学习笔记)

SpringBoot整合Security和JWT

转自

https://blog.csdn.net/mengxianglong123/article/details/112463172
在这里插入图片描述

1.导入相关的依赖和基础配置

1.1导入依赖

	<parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.4.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--jtw-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
    </dependencies>

1.2 相关配置

# 生成token的相关配置
jwt:
  header: Authorization  #请求头
  base64Secret: wsl_  #盐值
  tokenValidityInSeconds: 3600000 #过期时间 10秒

token配置类

@Component
@Data
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {

    /** Request Headers : Authorization */
    private String header;

    /** Base64对该令牌进行编码 */
    private String base64Secret;

    /** 令牌过期时间 此处单位/毫秒 */
    private Long tokenValidityInSeconds;
}

实现security中的UserDetails类,方便后续使用

@Data
public class JwtUser implements UserDetails {   //实现UserDeails接口
    //用户名
    private String username;
    //密码
    private String password;
    // 权限(角色)列表
    Collection<? extends GrantedAuthority> authorities;

    public JwtUser(String stuId, String password, List<GrantedAuthority> grantedAuthorities) {
        this.username = stuId;
        this.password = password;
        this.authorities = grantedAuthorities;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

token 生成工具,用来生成和解析token

@Component
public class JWTUtils {

    @Resource
    private  JwtProperties jwtProperties;


    /**
     * 生成token
     * @param name 用户名称
     * @return
     */
    public  String generateToken(String name){
        Map<String,Object> map = new HashMap<>();
        map.put("username",name);
//        map.put("password",user.getPassword());
        return Jwts.builder()
                //设置用户信息
                .setClaims(map)
                //token过期时间
                .setExpiration(new Date(System.currentTimeMillis()+jwtProperties.getTokenValidityInSeconds()))
                //设置主题
                .setSubject("Wsl_system")
                //设置签名
                .signWith(SignatureAlgorithm.HS512,jwtProperties.getBase64Secret().getBytes(StandardCharsets.UTF_8))
                .compact();
    }

    /**
     * 解析token
     * @param token token
     */
    public   String analysisToken(String token){
        Claims body = Jwts.parser()
                .setSigningKey(jwtProperties.getBase64Secret().getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();

        return body.get("username").toString();
    }


}

2.创建自定义权限不足处理类

实现AccessDeniedHandler类,重写里面的handle方法,源码中的注解
在这里插入图片描述

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Map<String,Object> ret = new HashMap<>();
        ret.put("code",401);
        ret.put("message","权限不足");
        ret.put("data",null);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(ret));
    }
}

3.创建认证失败处理类

实现AuthenticationEntryPoint接口重写这个commence方法,用于验证token失败之后会进入到这个commence方法中。源码注释
在这里插入图片描述

@Configuration
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Map<String,Object> ret = new HashMap<>();
        ret.put("code",401);
        ret.put("message","无效token");
        ret.put("data",null);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(ret));
    }
}

4.创建登录成功处理器

实现AuthenticationSuccessHandler接口,用户登录成功之后进入到onAuthenticationSucess方法,主要生成token,将token给到前端。源码注释
在这里插入图片描述

@Configuration
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {


    @Autowired
    private JWTUtils jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        String name = authentication.getName();
        //生成token
        String token = jwtUtils.generateToken(name);

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

        Map<String,Object> ret = new HashMap<>();
        ret.put("code",200);
        ret.put("message","登录成功");
        ret.put("data",token);
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ret));
    }
}

5.创建Token过滤器

继承OncePerRequestFilter类,重写doFilterInternal方法。主要作用是用户登录完成之后,拿着token来访问资源,对token的解析

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUserServiceImpl jwtUserService;

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private JWTUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader(jwtProperties.getHeader());
        //解析token
        try {
            String name = jwtUtils.analysisToken(token);
            //当token中的username不为空时进行验证token是否是有效的token
            if (name != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = jwtUserService.loadUserByUsername(name);
                //认证
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            filterChain.doFilter(request,response);
        }
    }
}

6.自定义登录逻辑

实现UserDetailsService接口,重写loadUserByUsername方法。这里暂时写死了,正常做法应该查询数据库。源码部分太多。

@Component
public class JwtUserServiceImpl  implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        //占时写死 admin
        if(!"admin".equals(name)){
            throw new UsernameNotFoundException("用户名或密码不存在");
        }
        List<GrantedAuthority> list = new ArrayList<>();
        //设置admin角色 角色前面加 ROLE_
        list.add(new SimpleGrantedAuthority("ROLE_admin"));
//        list.add(new SimpleGrantedAuthority("add"));
        return new JwtUser(
                name,
                passwordEncoder.encode("123"),
                list
        );
    }
}

7.创建登录失败后的处理器

实现AuthenticationFailureHandler 接口重写onAuthenticationFailure方法。主要登录失败了之后给友好的提示.源码注释如下:
在这里插入图片描述

@Configuration
public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        Map<String,Object> ret = new HashMap<>();
        ret.put("code",401);
        ret.put("message","用户名或密码错误");
        ret.put("data",null);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(ret));
    }
}

8.配置WebSecurityConfigurer类

继承WebSecurityConfigrerAdaper 重写configure(HttpSecurity http)方法和将AuthenticationManager注入到Spring中。将上面的处理器和过滤器添加到这个配置类中,以及配置拦截和放行路径

@Configuration
@EnableWebSecurity
//开启注解权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;



    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .successHandler(jwtAuthenticationSuccessHandler)
                .failureHandler(loginFailureHandler)
                .loginProcessingUrl("/login")
                //关闭csrf
                .and()
                .csrf().disable()
                // 自定义认证失败类
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                // 自定义权限不足处理类
                .accessDeniedHandler(jwtAccessDeniedHandler).and()
                //设置无状态登录
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //过滤登录退出请求
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
                .antMatchers("/login/**").permitAll()
                //所有请求都需要拦截
                .anyRequest()
                .authenticated();
		//添加token验证过期器,并指定过滤器的类型是UsernamePasswordAuthenticationFilter
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

9编写测试接口

@RequestMapping("/test")
@RestController
public class TestController {

    @GetMapping("/get")
    public String  get(){
        return "SUCCESS";
    }

    @GetMapping("/delete")
    //需要admin角色
    @PreAuthorize("hasAnyRole('admin')")
    public String delete(){
        return "操作成功";
    }


    @PostMapping("/add")
    //需要add权限
    @PreAuthorize("hasAnyAuthority('add')")
    public String add(){
        return "操作成功";
    }

}

10 测试

先登录
在这里插入图片描述
带着token访问
在这里插入图片描述
不带token访问
在这里插入图片描述
访问没有权限的接口
在这里插入图片描述

推荐讲解Security原理比较好的一个博主

https://blog.csdn.net/qq_40794973/category_9356579.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值