详解Springboot中如何集成Spring Security JWT实现接口权限认证

2021-08-16 13:55:58 浏览数 (4280)

本篇文章主要介绍了在Springboot项目中集成Spring-Security JWT实现接口权限认证的详细步骤,对于正在学习Springboot框架的小伙伴有一定的学习参考的价值。

1、添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

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

2、集成JWT工具类(JwtUtils)

package com.dreamteam.chdapp.utils;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author HeYunHui
 * @create 2020/11/15 14:12
 */
public class JwtUtils {
    private static final Logger logger= LoggerFactory.getLogger(JwtUtils.class);
    public static  final long EXPIRATION_TIME=60*60*1000;// 令牌环有效期
    public static final String SECRET="abc123456def";//令牌环密钥
    public static final String TOKEN_PREFIX="Bearer";//令牌环头标识
    public static final String HEADER_STRING="Passport";//配置令牌环在http heads中的键值
    public static final String ROLE="ROLE";//自定义字段-角色字段

    //生成令牌环
    public static String generateToken(String userRole,String userid){
        HashMap<String,Object> map=new HashMap<>();
        map.put(ROLE,userRole);
        map.put("userid",userid);
        String jwt= Jwts.builder()
                .setClaims(map)
                .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512,SECRET)
                .compact();
        return TOKEN_PREFIX+" "+jwt;
    }
    //生成令牌环
    public static String generateToken(String userRole,String userid,long exprationtime){
        HashMap<String,Object> map=new HashMap<>();
        map.put(ROLE,userRole);
        map.put("userid",userid);
        String jwt= Jwts.builder()
                .setClaims(map)
                .setExpiration(new Date(System.currentTimeMillis()+exprationtime))
                .signWith(SignatureAlgorithm.HS512,SECRET)
                .compact();
        return TOKEN_PREFIX+" "+jwt;
    }

    //令牌环校验
    public static Map<String,Object> validateTokenAndGetClaims(HttpServletRequest request){
        String token=request.getHeader(HEADER_STRING);
        if(token==null){
            throw new TokenValidationException("Missing Token");

        }
        else{
            Map<String,Object> body= Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX,""))
                    .getBody();
            return body;
        }
    }
    
    static class TokenValidationException extends RuntimeException{
        public TokenValidationException(String msg){
            super(msg);
        }
    }
}

3、集成JWT filter(拦截器/过滤器)

package com.dreamteam.chdapp.filter;

import com.dreamteam.chdapp.utils.JwtUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

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.Arrays;
import java.util.Map;

import static com.dreamteam.chdapp.utils.JwtUtils.ROLE;

/**
 * @Author HeYunHui
 * @create 2020/11/15 14:46
 */
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private static final PathMatcher pathmatcher = new AntPathMatcher();
    private String[] protectUrlPattern = {"/manage/**", "/member/**", "/auth/**"}; //哪  些请求需要进行安全校验

    public JwtAuthenticationFilter() {

    }


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//是不是可以在这里做多种方式登录呢


        try {
            if (isProtectedUrl(httpServletRequest)) {
                Map<String, Object> claims = JwtUtils.validateTokenAndGetClaims(httpServletRequest);
                String role = String.valueOf(claims.get(ROLE));
                String userid = String.valueOf(claims.get("userid"));
                //最关键的部分就是这里, 我们直接注入了
                SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
                        userid, null, Arrays.asList(() -> role)
                ));

            }
        } catch (Exception e) {
            e.printStackTrace();
            httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
            return;
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);


    }

    //是否是保护连接
    private boolean isProtectedUrl(HttpServletRequest request) {

        boolean flag = false;
        for (int i = 0; i < protectUrlPattern.length; i++) {
            if (pathmatcher.match(protectUrlPattern[i], request.getServletPath())) {
                return true;
            }
        }
        return false;
    }
}

4、配置JWT config类(配置类)

跨域访问:客户端与服务端域名不同或是端口号不同。防止跨域攻击

package edu.ynmd.cms.config;

import edu.ynmd.cms.filter.JwtAuthenticationFilter;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowUrlEncodedSlash(true);
        return firewall;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .cors()  //允许跨域访问
                .and()
                .authorizeRequests()
                .antMatchers("/").authenticated() //配置那些url需要进行校验--所有请求都需要校验"/"


                .antMatchers("/public/**").permitAll() //那些请求不需要校验

                .anyRequest().authenticated() //自定义校验类
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(),
                        UsernamePasswordAuthenticationFilter.class)
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//关闭session
        ;
    }
}

5、Action注解

在Controller类中添加

@CrossOrigin
@RestController
@PreAuthorize("hasAuthority('admin')") //配置角色,拥有该角色的用户方可访问
@RequestMapping("/manage")

postman测试http://localhost:7070/manage/userList,不可访问

在这里插入图片描述

public开头的可以访问

6、token令牌环,访问需校验的资源

public的Controller类添加

    @PostMapping("/login")
    @ResponseBody
    public HashMap<String,String> login(
            @RequestBody Account account) throws IOException {
//        Users u=manageService.getUserByUserNameAndPass(account.username,account.password);
        if(account.username.equals("admin")&&account.password.equals("123456")){
//        if(u!=null){
            String jwt= JwtUtils.generateToken("admin","123456789abc");
//            String jwt= JwtUtils.generateToken(u.getRoleid(),u.getUsersid());


            return new HashMap<String,String>(){{
                put("msg","ok");
                put("token",jwt);
//                put("role",u.getRoleid());
                put("role","admin");
            }};
        }
        else {
            //return new ResponseEntity(HttpStatus.UNAUTHORIZED);
            return new HashMap<String,String>(){{
                put("msg","error");
                put("token","error");
            }};
        }
    }

    public static class Account{
        public String username;
        public String password;
    }

postman测试,随便输用户名密码

在这里插入图片描述

输入代码中的用户名密码

在这里插入图片描述

去JWT官网https://jwt.io/,页面下滑,将得到的token输入,得到

在这里插入图片描述

manage的Controller类中添加测试

    @GetMapping("testSecurityResource")
    @ResponseBody
    public String testSecurityResource() throws Exception{
        
        return "受保护的资源";
    }

用postman访问http://localhost:7070/manage/testSecurityResource,返回结果

在这里插入图片描述

7、service工具类

通用请求处理

package com.dreamteam.chdapp.controller.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;

/**
 * 通用请求处理
 * @Author HeYunHui
 * @create 2020/11/14 15:38
 */
@Controller
public class CommonController {

    protected static final Logger log= LoggerFactory.getLogger(CommonController.class);

    /**
     * 字符串为空
     * @param value
     * @return
     */
    public static boolean isNullOrSpace(String value){

        if(value==null){
            return true;
        }
        else {
            if(value.equals("")){
                return true;
            }
            else {
                return false;
            }
        }
    }
}

Service层

String getCurrentUserId();//从令牌环中获取userid
String getCurrentRole();//从令牌环中获取角色id

ServiceImpl

/**
     * 获取当前登录用的的Id
     * @return
     */
    @Override
    public String getCurrentUserId() {
        String userid= (String) SecurityContextHolder.getContext().getAuthentication() .getPrincipal();
        if(CommonController.isNullOrSpace(userid)){
            return null;
        }
        else {
            return userid;
        }
    }

    /**
     * 获取当前登录用户的角色
     * @return
     */
    @Override
    public String getCurrentRole() {
        String role=null;
        Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>)    SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();

        }

        if(CommonController.isNullOrSpace(role)){
            return null;
        }
        else{
            return role;
        }
    }

修改manage的Controller类

    @GetMapping("testSecurityResource")
    @ResponseBody
    public String testSecurityResource() throws Exception{

        String userid=userInfoService.getCurrentUserId();
        String role=userInfoService.getCurrentRole();

        return "受保护的资源,当前用户的id是"+userid+"当前用户的角色是"+role;

    }

用postman测试

在这里插入图片描述

这是前面自定义的

在这里插入图片描述

8、识别token信息

在这里插入图片描述

如果将下图中的角色换掉,将不能访问

在这里插入图片描述

9、自动更新令牌环

添加Controller类

package com.dreamteam.chdapp.controller;

import com.dreamteam.chdapp.controller.common.CommonController;
import com.dreamteam.chdapp.utils.JwtUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;

/**
 * 令牌环自动更新
 * @Author HeYunHui
 * @create 2020/11/16 17:24
 * @PreAuthorize("hasAuthority('admin')")//只允许有admin角色的用户访问 hasAnyAuthority([auth1,auth2])
 */
@CrossOrigin
@RestController
@PreAuthorize("hasAnyAuthority('admin','member')")
@RequestMapping("/auth")
public class AuthController {
    /**
     * 更新令牌环信息
     * @param request
     * @return
     */
    @GetMapping("refreshToken")
    @ResponseBody
    public HashMap<String,String> refreshToken(HttpServletRequest request){


        String role=null;
        Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>)    SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();

        }
        // UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication() .getPrincipal();
        String userid= (String)SecurityContextHolder.getContext().getAuthentication() .getPrincipal();
        if(CommonController.isNullOrSpace(role)){
            return new HashMap<String,String>(){{
                put("token","error");
            }};
        }
        else{

            String jwt="";
            //一小时
            jwt= JwtUtils.generateToken(role,userid,60*60*1000);
            HashMap<String,String> m=new HashMap<>();
            m.put("token",jwt);
            return m;


        }

    }

    /**
     * 获取当前登录用户的角色
     * @return
     */
    @GetMapping("getRole")
    @ResponseBody
    public HashMap<String,String> getRoleByToken(){

        String role="";
        String userid="";
        Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>)    SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();

        }
        if(CommonController.isNullOrSpace(role)){
            return new HashMap<String,String>(){{
                put("role","error");
            }};
        }
        else{

            HashMap<String,String> m=new HashMap<>();
            m.put("role",role);
            return m;
        }
    }
}

用postman测试

在这里插入图片描述

10、使用数据库存储用户信息

(1)实体类

package com.dreamteam.chdapp.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor


/**
 * 表名
 */
@TableName("users")
public class Users {

    @TableId(type = IdType.AUTO)
    private String usrId;
    private String usrName;

    private String usrTel;

    private String usrPwd;

    private String usrType;
    
}

UserMapper

package com.dreamteam.chdapp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dreamteam.chdapp.entity.Users;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author HeYunHui
 * @create 2020/11/11 21:50
 */
@Repository
@Mapper
public interface UserMapper extends BaseMapper<Users> {

    List<Users> getUsersByUsrNameAndPwd(@Param("usrName")String usrName, @Param("usrPwd") String usrPwd);
}

UsersMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dreamteam.chdapp.mapper.UserMapper">

    <select id="getUsersByUsrNameAndPwd" resultType="com.dreamteam.chdapp.entity.Users">
        select * from users where #{usrName}=usr_name and #{usrPwd}=usr_pwd
    </select>
</mapper>

service

Users getUsersByUsrNameAndPwd(String usrName,String usrPwd);

serviceImpl JWT获取用户名密码

    @Override
    public Users getUsersByUsrNameAndPwd(String usrName, String usrPwd) {
        List<Users> ul=userMapper.getUsersByUsrNameAndPwd(usrName,usrPwd);
        if(ul.size()>0){
            return ul.get(0);
        }
        return null;
    }

Controller

    @PostMapping("/login")
    @ResponseBody
    public HashMap<String,String> login(
            @RequestBody Account account) throws IOException {
        Users u=userInfoService.getUsersByUsrNameAndPwd(account.username,account.password);
//        if(account.username.equals("admin")&&account.password.equals("123456")){
        if(u!=null){
//            String jwt= JwtUtils.generateToken("admin","123456789abc");
            String jwt= JwtUtils.generateToken(u.getUsrType(),u.getUsrId());


            return new HashMap<String,String>(){{
                put("msg","ok");
                put("token",jwt);
                put("role",u.getUsrType());
//                put("role","admin");
            }};
        }
        else {
            //return new ResponseEntity(HttpStatus.UNAUTHORIZED);
            return new HashMap<String,String>(){{
                put("msg","error");
                put("token","error");
            }};
        }
    }

    public static class Account{
        public String username;
        public String password;
    }

postman测试

a.登录,生成token

在这里插入图片描述

b.输入token访问manage下的链接

在这里插入图片描述

以上就是关于Springboot集成Spring Security JWT实现接口权限认证的详细内容,想要了解更多关于Springboot接口权限认证的其他内容,可以多多关注W3Cschool相关内容的文章。