SpringBoot和SpringSecurity整合一下JWT

20 篇文章 1 订阅
2 篇文章 0 订阅

上一篇写了SpringBoot整合SpringSecurity实现了登录认证和授权,但是SpringSecurity是把用户信息存储在session中的。对服务器有一定的压力,所以目前JWT这种服务无状态的校验方式比较流行

我接着上篇的代码改进,将JWT融合进去

首先导入JWT的maven坐标,我使用的是jjwt
   <properties>
        <java.version>1.8</java.version>
        <jjwt.version>0.10.6</jjwt.version>
    </properties>
    
 <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
先搞一个JwtTokenUtil类,里面一些创建token,获取权限,获取用户名等
package com.example.springsecurity.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-02 14:08
 **/
public class JwtTokenUtils {

    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";

    private static final String SECRET = "ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=";
    private static final String ISS = "echisan";

    // 过期时间是3600秒,既是1个小时
    private static final long EXPIRATION = 3600L;

    // 选择了记住我之后的过期时间为7天
    private static final long EXPIRATION_REMEMBER = 604800L;

    // 添加角色的key
    private static final String ROLE_CLAIMS = "rol";

    // 创建token
    public static String createToken(String username, String permission,boolean isRememberMe) {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET);
        Key key = Keys.hmacShaKeyFor(keyBytes);

        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, permission);
        return Jwts.builder()
                .signWith(key,SignatureAlgorithm.HS512)
                // 这里要早set一点,放到后面会覆盖别的字段
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .compact();
    }

    // 从token中获取用户名
    public static String getUsername(String token) {
        return getTokenBody(token).getSubject();
    }

    // 是否已过期
    public static boolean isExpiration(String token) {
        return getTokenBody(token).getExpiration().before(new Date());
    }

    private static Claims getTokenBody(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    }

    public static String getUserPermission(String token){
        return  (String) getTokenBody(token).get(ROLE_CLAIMS);
    }
}


其中注意 SECRET 不能太短,太短不符合JWT生产Token秘钥的安全标准,必须使用最少88位的Base64对该令牌进行编码

写一个JwtUser类,用来记录用户信息
package com.example.springsecurity.model;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-02 14:18
 **/
public class JwtUser implements UserDetails {

    private Integer id;
    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;

    public JwtUser() {
    }

    // 写一个能直接使用user创建jwtUser的构造器
    public JwtUser(SysUser user) {
        if(null == user){
            return ;
        }
        id = user.getId();
        username = user.getUserName();
        password = user.getPassword();
        authorities = Collections.singleton(new SimpleGrantedAuthority(user.getPermission()));
    }

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

    @Override
    public String getPassword() {
        return password;
    }

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

    // 账号是否未过期,默认是false,记得要改一下
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 账号是否未锁定,默认是false,记得也要改一下
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 账号凭证是否未过期,默认是false,记得还要改一下
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 这个有点抽象不会翻译,默认也是false,记得改一下
    @Override
    public boolean isEnabled() {
        return true;
    }

    // 我自己重写打印下信息看的
    @Override
    public String toString() {
        return "JwtUser{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", authorities=" + authorities +
                '}';
    }
}

修改MyUserDetailsService,返回值就变成JwtUser了
package com.example.springsecurity.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.springsecurity.mapper.UserMapper;
import com.example.springsecurity.model.JwtUser;
import com.example.springsecurity.model.SysUser;
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.Component;


/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-01 15:02
 **/
@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //这里可以可以通过username(登录时输入的用户名)然后到数据库中找到对应的用户信息,并构建成我们自己的UserInfo来返回。
        //数据库中的密码是加密后的
        QueryWrapper<SysUser> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.eq("user_name",username);
        SysUser sysUser = userMapper.selectOne(objectQueryWrapper);

        return new JwtUser(sysUser);
    }
}
因为现在是JWT做权限校验,所有要新增两个过滤器
登录成功生产token,失败输出异常信息过滤类
package com.example.springsecurity.config.jwtfilter;

import com.example.springsecurity.model.JwtUser;
import com.example.springsecurity.model.SysUser;
import com.example.springsecurity.utils.JwtTokenUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

/**
 * @program: spring-security
 * @description: 该拦截器用于获取用户登录的信息,只需创建一个token并调用authenticationManager.authenticate()让spring-security去进行验证就可以了,
 *               不用自己查数据库再对比密码了,这一步交给spring去操作。
 * @author: fbl
 * @create: 2020-12-02 14:21
 **/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        // 从输入流中获取到登录的信息
        try {
            SysUser loginUser = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUserName(), loginUser.getPassword(), new ArrayList<>())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        // 查看源代码会发现调用getPrincipal()方法会返回一个实现了`UserDetails`接口的对象
        // 所以就是JwtUser啦
        JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
        System.out.println("jwtUser:" + jwtUser.toString());

        String permission = "";
        // 因为在JwtUser中存了权限信息,可以直接获取,由于只有一个角色就这么干了
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        for (GrantedAuthority authority : authorities){
            permission = authority.getAuthority();
        }
        String token = JwtTokenUtils.createToken(jwtUser.getUsername(),permission, false);
        // 返回创建成功的token
        // 但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的格式应该是 `Bearer token`
        response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
    }

    // 这是验证失败时候调用的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("authentication failed, reason: " + failed.getMessage());
    }
}

注意这个过滤类我们在JWTAuthenticationFilter这个构造方法上新增了一行代码让我们有登录接口

  public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        // 这行代码就是登录接口
        super.setFilterProcessesUrl("/auth/login");
    }
调用接口验证token权限过滤类
package com.example.springsecurity.config.jwtfilter;

import com.example.springsecurity.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

/**
 * @program: spring-security
 * @description: 验证成功当然就是进行鉴权了,每一次需要权限的请求都需要检查该用户是否有该权限去操作该资源,当然这也是框架帮我们做的,那么我们需要做什么呢?
 * 很简单,只要告诉spring-security该用户是否已登录,是什么角色,拥有什么权限就可以了。
 * @author: fbl
 * @create: 2020-12-02 14:25
 **/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request, response, chain);
    }

    // 这里从token中获取用户信息并新建一个token
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");

        String username = JwtTokenUtils.getUsername(token);
        String permission = JwtTokenUtils.getUserPermission(token);
        if (username != null) {
            return new UsernamePasswordAuthenticationToken(username, null, Collections.singleton(new SimpleGrantedAuthority(permission)));
        }
        return null;
    }
}

在SecurityConfig中将这两个过滤器配置进去
package com.example.springsecurity.config;

import com.example.springsecurity.config.jwtfilter.JWTAuthenticationFilter;
import com.example.springsecurity.config.jwtfilter.JWTAuthorizationFilter;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-01 14:40
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationProvider provider;  //注入我们自己的AuthenticationProvider


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 关闭csrf防护
                .csrf().disable()
                .headers().frameOptions().disable()
                .and();

        http
                .authorizeRequests() // 授权配置
                //无需权限访问
                .antMatchers("/css/**", "/error404").permitAll()
                .anyRequest().authenticated() // anyRequest 只能配置一个,多个会报错
                // 添加自定义权限表达式
                //.anyRequest().access("@rbacService.hasPermission(request,authentication)")    //必须经过认证以后才能访问
                //其他接口需要登录后才能访问
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }


    /**
     * 自定义用户名和密码有2种方式,一种是在代码中写死 另一种是使用数据库
     */

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

    /**
     * 可以自己配置用户名 密码 角色 权限
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(provider);
    }

}

还有我们的controller
package com.example.springsecurity.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-01 14:23
 **/
@Controller
public class AuthController {

    /**
     * 跳转首页
     */
    @GetMapping("")
    public void index1(HttpServletResponse response){
        //内部重定向
        try {
            response.sendRedirect("/index");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @RequestMapping("/index")
    @ResponseBody
    public String index() {
        return "登录成功";
    }

    @RequestMapping("/loginError")
    @ResponseBody
    public String loginError() {
        return "登录失败";
    }

    @RequestMapping("/loginPage")
    public String login() {
        return "login_page";
    }

    /**
     * 为了方便测试,我们调整添加另一个控制器 /whoim 的代码 ,让他返回当前登录的用户信息,
     * 前面说了,他是存在SecurityContextHolder 的全局变量中,所以我们可以这样获取
     */
    @RequestMapping("/who")
    @ResponseBody
    @PreAuthorize("hasAuthority('user')")
    public Object who(){
        return "我是" + SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

这样就差不多了,我们结合postMan测试一下

首先没有登录,去访问index接口,理所当然的403,没有权限
在这里插入图片描述
然后我们试试登录,拿到token再去访问index接口
在这里插入图片描述访问登录接口,输入正确的用户名密码,可以看到在Header生产token。我们拿到去访问index
注意粘token的时候不要粘到token的前缀Bearer ,它后面的空格也不可以粘到

在这里插入图片描述
输入正确的token成功访问,如果token输错也是403

接下来我们对权限进行测试
  /**
     * 为了方便测试,我们调整添加另一个控制器 /whoim 的代码 ,让他返回当前登录的用户信息,
     * 前面说了,他是存在SecurityContextHolder 的全局变量中,所以我们可以这样获取
     */
    @RequestMapping("/who")
    @ResponseBody
    @PreAuthorize("hasAuthority('user')")
    public Object who(){
        return "我是" + SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }

接口who要有user权限才能访问:
在这里插入图片描述
但我们的用户是common权限,试一下能不能访问
在这里插入图片描述果然是403
我们将数据库中的用户权限改为user,再次测试(记得要重新登录,重新生成token)
在这里插入图片描述

在这里插入图片描述访问成功
--------------------------------我是分割线-----------------------------------------------

接着更新一个登录接口和一个带有权限访问接口的controller层,我们使用接口登录然后验证授权,不适用上述登录验证过滤器,不过权限验证还是要权限过滤器的

首先我们先把MyAuthenticationProvider这个类的 @Component注解注释掉,或者删掉这个类,不然登录不调用登录接口里面的方法,而是直接进入这个类判定账号密码(这个问题找了一上午,登录接口配置放行,怎么访问都是403,因为你登录走的这个类,又没有往SpringSecurity里塞用户名密码,一直都是用户名不存在)在这里插入图片描述
将SpringSecurity配置文件SecurityConfig注入的MyAuthenticationProvider删去,放行login接口的访问权限,删去JWTAuthenticationFilter(登录验证过滤器)
package com.example.springsecurity.config;

import com.example.springsecurity.config.jwtfilter.JWTAuthorizationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-01 14:40
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 禁用 CSRF
                .csrf().disable()

                // 防止iframe 造成跨域
                .headers()
                .frameOptions()
                .disable()

                // 不创建会话
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and();

        http
                .authorizeRequests() // 授权配置
                //无需权限访问
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated() // anyRequest 只能配置一个,多个会报错
                //其他接口需要登录后才能访问
                .and()
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));

    }


    /**
     * 自定义用户名和密码有2种方式,一种是在代码中写死 另一种是使用数据库
     */

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

}

写一个controller层的接口
package com.example.springsecurity.controller;

import com.example.springsecurity.model.dto.UserDto;
import com.example.springsecurity.model.vo.UserVo;
import com.example.springsecurity.result.Result;
import com.example.springsecurity.service.impl.LoginServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-08 07:56
 **/

@RestController
public class LoginController {

    @Autowired
    LoginServiceImpl loginService;

    @PostMapping("/login")
    public Result login(@RequestBody @Validated UserDto user){
        return loginService.login(user);
    }

    @GetMapping("/test")
    @PreAuthorize("hasAnyAuthority('admin','common')")
    public String test(){
        return "测试权限";
    }
}

类中的userVo属性是userName,password,token,userDto是userName,password
package com.example.springsecurity.service.impl;

import com.example.springsecurity.mapper.UserMapper;
import com.example.springsecurity.model.JwtUser;
import com.example.springsecurity.model.SysUser;
import com.example.springsecurity.model.dto.UserDto;
import com.example.springsecurity.model.vo.UserVo;
import com.example.springsecurity.result.ErrorCodeEnum;
import com.example.springsecurity.result.Result;
import com.example.springsecurity.utils.JwtTokenUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.stream.Collectors;

/**
 * @program: spring-security
 * @description:
 * @author: fbl
 * @create: 2020-12-08 08:14
 **/
@Service
public class LoginServiceImpl {

    @Autowired
    UserMapper userMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;


    public Result login(UserDto user) {
        String userName = user.getUserName();
        String password = user.getPassword();
        // 用户名是否存在
        SysUser userInfo = userMapper.findUserByName(userName);
        JwtUser jwtUser = new JwtUser(userInfo);

        if (null == userInfo) {
            return Result.failure(ErrorCodeEnum.SYS_ERR_LOGIN_FAIL);
        }
        boolean flag = passwordEncoder.matches(password, userInfo.getPassword());
        if (!flag) {
            return Result.failure(ErrorCodeEnum.SYS_ERR_LOGIN_FAIL);
        }
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();

        String strAuthorities = authorities.stream().map(a -> ((GrantedAuthority) a).getAuthority()).collect(Collectors.joining(","));
        String token = JwtTokenUtils.createToken(userName, strAuthorities, false);

        // 模型转换
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        userVo.setPassword(userInfo.getPassword());
        userVo.setToken(token);
        return Result.success(userVo);
    }

}

因为以前写的demo一个用户只有一个角色,现在要改成多个角色,部分代码改动
  1. 首先权限验证过滤器中,要过滤多个权限JWTAuthorizationFilter
package com.example.springsecurity.config.jwtfilter;

import com.example.springsecurity.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @program: spring-security
 * @description: 验证成功当然就是进行鉴权了,每一次需要权限的请求都需要检查该用户是否有该权限去操作该资源,当然这也是框架帮我们做的,那么我们需要做什么呢?
 * 很简单,只要告诉spring-security该用户是否已登录,是什么角色,拥有什么权限就可以了。
 * @author: fbl
 * @create: 2020-12-02 14:25
 **/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request, response, chain);
    }

    // 这里从token中获取用户信息并新建一个token
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");

        String username = JwtTokenUtils.getUsername(token);
        String permission = JwtTokenUtils.getUserPermission(token);
        ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        for (String p : Arrays.asList(permission.split(","))) {
            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(p));
        }

        if (username != null) {
            return new UsernamePasswordAuthenticationToken(username, null, simpleGrantedAuthorities);
        }
        return null;
    }
}

在这里插入图片描述
2. 其次,创建token的时候也要放入多个权限,(多个权限逗号分隔,放入map中)

 // 创建token
    public static String createToken(String username, String permission,boolean isRememberMe) {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET);
        Key key = Keys.hmacShaKeyFor(keyBytes);

        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, permission);
        return Jwts.builder()
                .signWith(key,SignatureAlgorithm.HS512)
                // 这里要早set一点,放到后面会覆盖别的字段
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .compact();
    }

测试均可成功,就不展示了

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,这是一个比较常见的需求,下面是大致的步骤: 1. 在pom.xml中引入spring-securityjwt相关依赖 ```xml <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring-security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security.version}</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency> ``` 其中 `${spring-security.version}` 和 `${jjwt.version}` 分别为 Spring SecurityJWT 的版本号。 2. 配置 Spring SecuritySpring Boot 中,使用 Java Config 来配置 Spring Security。具体的配置可以参考如下代码: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**") .permitAll() .anyRequest() .authenticated() .and() .exceptionHandling() .authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 上面的代码中,我们配置了: - `userDetailsService`:用于从数据库中读取用户信息。 - `JwtAuthenticationEntryPoint`:用于处理认证失败的情况。 - `JwtAuthenticationFilter`:用于处理 JWT 认证。 在 `configure(HttpSecurity http)` 中,我们配置了哪些请求需要认证,哪些请求不需要认证,以及异常处理和 session 管理等。 3. 配置 JWTJWT 中,我们需要定义一个 secret key 用于签名和验证 JWT。可以在 application.properties 中配置: ```properties jwt.secret=mySecretKey jwt.expirationMs=86400000 ``` 其中,`jwt.secret` 是用于签名和验证 JWT 的 secret key,`jwt.expirationMs` 是 JWT 的过期时间(单位为毫秒)。 然后,我们可以定义一个 `JwtUtils` 类来生成和解析 JWT: ```java @Component public class JwtUtils { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expirationMs}") private int jwtExpirationMs; public String generateJwtToken(Authentication authentication) { UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); return Jwts.builder() .setSubject(userPrincipal.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } public String getUsernameFromJwtToken(String token) { return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); } public boolean validateJwtToken(String authToken) { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); return true; } catch (SignatureException e) { logger.error("Invalid JWT signature: {}", e.getMessage()); } catch (MalformedJwtException e) { logger.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { logger.error("JWT token is expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { logger.error("JWT token is unsupported: {}", e.getMessage()); } catch (IllegalArgumentException e) { logger.error("JWT claims string is empty: {}", e.getMessage()); } return false; } } ``` 上面的代码中,我们使用了 `Jwts.builder()` 和 `Jwts.parser()` 来生成和解析 JWT。 4. 配置认证接口 最后,我们可以在认证接口中生成 JWT 并返回给客户端: ```java @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsService userDetailsService; @PostMapping("/signin") public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtUtils.generateJwtToken(authentication); UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername()); return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getUsername(), userDetails.getAuthorities())); } } ``` 上面的代码中,我们使用了 `AuthenticationManager` 来进行认证,然后使用 `JwtUtils` 生成 JWT 并返回给客户端。 以上就是整合 Spring SecurityJWT 的大致步骤,具体实现过程还需根据实际情况进行调整。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值