OpenAPI鉴权(二)jwt鉴权

news2024/11/25 9:51:07

一、思路

前端调用后端可以使用jwt鉴权;调用三方接口也可以使用jwt鉴权。对接多个三方则与每个third parth都约定一套token规则,因为如果使用同一套token,token串用可能造成权限越界问题,且payload交叉业务不够清晰。下面的demo包含了两套jwt,前端和一个三方(openApi)的:

1、token生成:

(1)签发给前端的token在本项目生成;

(2)签发给第三方的token,由第三方根据双方约定的算法、密钥和payload通信信息自己生成

      。不能调用本项目(被调用方)接口生成,否则这个生成token的接口需要加白名单,

      会造成接口攻击和token泄露的安全问题。

2、token校验:

   先判断是哪个业务的token,再用各自约定的算法和业务规则校验。这里是根据url来判断的,

   不能只根据audience来判断,如用前端的token访问open api接口,从token解析出audience

    是前端的,再用前端的规则校验,校验通过访问成功;

二、demo

1、pom与配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>openapi-ta</artifactId>
        <groupId>us.zoom.openapi</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>openapi-ta-mas</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <ta.product.name>Mas</ta.product.name>
    </properties>

    <dependencies>
        <dependency>
            <groupId>us.zoom.openapi</groupId>
            <artifactId>openapi-ta-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>us.zoom.openapi.mas</groupId>
            <artifactId>open-api-mas-common</artifactId>
            <version>1.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.13.0</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <skip>true</skip>
                    <executable>false</executable>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire.version}</version>
                <configuration>
                    <systemPropertyVariables>
                        <org.uncommons.reportng.escape-output>false</org.uncommons.reportng.escape-output>
                        <spring.profiles.active>${spring.profiles.active}</spring.profiles.active>
                        <ta.product.name>${ta.product.name}</ta.product.name>
                    </systemPropertyVariables>
                    <skip>false</skip>
                    <testFailureIgnore>false</testFailureIgnore>
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                        --add-opens java.base/java.lang=ALL-UNNAMED
                    </argLine>
                    <suiteXmlFiles>
                        <suiteXmlFile>${project.basedir}/src/main/resources/suites/${spring.profiles.active}/AlertRuleAPIBvtSuite.xml</suiteXmlFile>
                    </suiteXmlFiles>
                    <properties>
                        <usedefaultlisteners>false</usedefaultlisteners>
                    </properties>
                </configuration>

            </plugin>
        </plugins>


        <resources>

            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>application.yml</include>
                    <include>application-${spring.profiles.active}.yml</include>
                </includes>
            </resource>

            <resource>
                <directory>src/main/java</directory>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <!--default env-->
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <spring.profiles.active>dev</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>go</id>
            <properties>
                <spring.profiles.active>go</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>aw1</id>
            <properties>
                <spring.profiles.active>aw1</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us02</id>
            <properties>
                <spring.profiles.active>us02</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us03</id>
            <properties>
                <spring.profiles.active>us03</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us04</id>
            <properties>
                <spring.profiles.active>us04</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us05</id>
            <properties>
                <spring.profiles.active>us05</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us06</id>
            <properties>
                <spring.profiles.active>us06</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us07</id>
            <properties>
                <spring.profiles.active>us07</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>us01</id>
            <properties>
                <spring.profiles.active>us01</spring.profiles.active>
            </properties>
        </profile>
    </profiles>

</project>
server.port=6666
server.servlet.context-path=/jwtDemo
2、启动类 
package com.demo.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.jmx.support.RegistrationPolicy;

@SpringBootApplication
//解决报错MXBean already registered with name org.apache.commons.pool2:type=GenericObjectPool,name=pool
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class JWTApplication {

    public static void main(String[] args) {
        SpringApplication.run(JWTApplication.class, args);

    }
}
 3、全局配置

(1)webMVC

package com.demo.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {


    //设置跨域
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:9528");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

(2)security

package com.demo.security.config;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }

    /*public DefaultPasswordEncoder() {
        this(-1);
    }
    *//**
     * @param strength
     *            the log rounds to use, between 4 and 31
     *//*
    public DefaultPasswordEncoder(int strength) {
    }
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }*/
}

package com.demo.security.config;

import com.demo.security.filter.LoginFilter;
import com.demo.security.filter.TokenAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityWebConfig {

    @Autowired
    private UserDetailsService userDetailsService;


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


    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain configure(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {

        http.csrf(AbstractHttpConfigurer::disable);
       http.headers(AbstractHttpConfigurer::disable);

       http.sessionManagement(sessionManagement -> {
           sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
       });
       http.authorizeRequests().anyRequest().authenticated().and()

               //1、登陆、退出url,均由前端拦截器控制,这里注释掉。
               //1.1、前端拦截器中判断缓存token为空,为空则post请求访问/login,目的是进入LoginFilter获取token
               //1.2、不为空则带token访问接口,如果AuthenticationFilter拦截token不合法则根据错误码跳转到登陆页面,重复1.1的操作
               //.logout().logoutUrl("/logout").and()
               //2、身份认证filter,访问系统(除了白名单接口)需要先登陆。post请求/login接口会进入这个拦截器
               // 校验用户名密码是否正确,正确返回token给前端,不正确则返回异常信息
               .addFilterBefore(new LoginFilter(authenticationManager), LoginFilter.class)
               //3、授权filer,authenticationManager为BasicAuthenticationFilter的必传参数。所有的接口都会走到这里
               // 根据用户id查询权限,连同身份一起塞入SecurityContextHolder全局变量,后面获取用户信息则直接从SecurityContextHolder中get
               .addFilterBefore(new TokenAuthenticationFilter(authenticationManager,userDetailsService),TokenAuthenticationFilter.class);
       return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers("/param/**", "/user-websocket-endpoint/**","/menu-websocket-endpoint/**");
    }
}
4、security数据源

(1)常量(模拟数据库)

package com.demo.security.constant;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模拟数据库查询数据,假设有:用户名/密码/角色/资源
 * admin/123/xtgly/user_manage、role_manage、menu_manage、school_manage
 * zs/123/userAdmin、roleAdmin/user_manage、role_manage、menu_manage
 * ls/123/schoolAdmin/school_manage
 */
public class UserDBConstants {

    public static Map<String, String> getUsers() {
        Map<String,String> users = new HashMap<>();
        users.put("admin","123");
        users.put("zs","123");
        users.put("ls","123");
        return users;
    }

    public static Map<String,List<String>> getUserRoles() {
        Map<String,List<String>> userRoles = new HashMap<>();
        //admin
        List<String> adminRoles = new ArrayList<>();
        adminRoles.add("xtgly");
        userRoles.put("admin",adminRoles);
        //zs
        List<String> zsRoles = new ArrayList<>();
        zsRoles.add("userAdmin");
        zsRoles.add("roleAdmin");
        userRoles.put("zs",zsRoles);
        //ls
        List<String> lsRoles = new ArrayList<>();
        lsRoles.add("schoolAdmin");
        userRoles.put("ls",lsRoles);
        return userRoles;
    }

    public static Map<String,List<String>> getUserPermissions() {
        Map<String,List<String>> userPermissions = new HashMap<>();
        List<String> lsPermissions = new ArrayList<>();
        //ls
        lsPermissions.add("school_manage");
        userPermissions.put("ls",lsPermissions);
        //zs
        List<String> zsPermissions = new ArrayList<>();
        zsPermissions.add("user_manage");
        zsPermissions.add("role_manage");
        zsPermissions.add("menu_manage");
        userPermissions.put("zs",zsPermissions);
        //admin
        List<String> adminPermissions = new ArrayList<>();
        adminPermissions.add("user_manage");
        adminPermissions.add("role_manage");
        adminPermissions.add("menu_manage");
        adminPermissions.add("school_manage");
        userPermissions.put("admin",adminPermissions);
        return userPermissions;
    }
}

(2)UserDetails

package com.demo.security.dto;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
@Data
public class UserDTO implements UserDetails {

    private Integer id;
    private String userName;
    private String userAccount;
    private List<String> roles;
    private List<String> menus;
    private String passWord;

    public UserDTO (Integer id,String userName,String userAccount,List<String> roles,List<String> menus,String passWord){
        this.id = id;
        this.userAccount = userAccount;
        this.userName = userName;
        this.roles = roles;
        this.menus = menus;
        this.passWord = passWord;
    }

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

    public List<String> getMenus() {
        return menus;
    }

    public void setMenus(List<String> menus) {
        this.menus = menus;
    }

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

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

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

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

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

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

(3)UserDetailService

package com.demo.security.service;


import com.demo.security.constant.UserDBConstants;
import com.demo.security.dto.UserDTO;
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.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;

/**
 * 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。
 * 而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑,
 */
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //模拟数据库查询
        Map<String, String> userMap = UserDBConstants.getUsers();
        String dbPwd = userMap.get(username);
        if(StringUtils.isEmpty(dbPwd)){
            throw new UsernameNotFoundException("用户不存在");
        }
        Map<String, List<String>> userRoles = UserDBConstants.getUserRoles();
        List<String> roles = userRoles.get(username);
        Map<String, List<String>> userMenus = UserDBConstants.getUserPermissions();
        List<String> menus = userMenus.get(username);
        UserDTO userDTO = new UserDTO(null,null,username,roles,menus,dbPwd);
        return userDTO;
    }
}
 5、filter

(1)前端登录

package com.demo.security.filter;

import com.demo.security.constant.UserDBConstants;
import com.demo.security.dto.ResponseMsg;
import com.demo.security.dto.UserDTO;
import com.demo.security.util.JwtUtil;
import com.google.gson.Gson;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
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.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

@Slf4j
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public LoginFilter(AuthenticationManager authenticationManager) {
        //super(new AntPathRequestMatcher("/login", "POST"));
        this.authenticationManager = authenticationManager;
    }

    /**
     * /login POST接口验证
     * @param req
     * @param res
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
        try {
            logger.info("进入LoginFilter");
            String userName = req.getParameter("userName");
            String passWord = req.getParameter("passWord");
            if (StringUtils.isEmpty(userName)) {
                throw new UsernameNotFoundException("请输入账号");
            }
            if (StringUtils.isEmpty(passWord)) {
                throw new UsernameNotFoundException("请输入密码");
            }
            //验证用户名密码是否正确
            Map<String, String> userMap = UserDBConstants.getUsers();
            if(!userMap.keySet().contains(userName)){
                throw new UsernameNotFoundException("用户不存在");
            }
            if(!passWord.equals(userMap.get(userName))){
                throw new UsernameNotFoundException("密码错误");
            }
            //这里权限返回空,由后面的授权过滤器查询
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(userName, passWord, new ArrayList<>()));
        } catch (UsernameNotFoundException e) {
            //返回错误信息
            res.setCharacterEncoding("UTF-8");
            res.setContentType("application/text;charset=utf-8");
            try {
                res.getWriter().write(e.getMessage());
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }


    @Override
    protected void successfulAuthentication(
            HttpServletRequest request,
            HttpServletResponse res,
            jakarta.servlet.FilterChain chain,
            Authentication authResult)
            throws IOException{
        UserDTO userDTO = (UserDTO) authResult.getPrincipal();
        String jwtToken = JwtUtil.generateWebToken(userDTO);
        //返
        ResponseMsg resMsg = ResponseMsg.builder().code(200).data(jwtToken).build();
        res.setContentType(ContentType.TEXT_HTML.toString());
        Gson gson = new Gson();
        res.getWriter().write(gson.toJson(resMsg));
    }
}

(2)token验证

package com.demo.security.filter;

import com.demo.security.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import java.io.IOException;

@Slf4j
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {

    private UserDetailsService userDetailsService;

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager, UserDetailsService userDetailsService) {
        super(authenticationManager);
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        logger.info("登陆成功后访问,url={}"+req.getRequestURI());
        String token = req.getHeader("token");
        res.setCharacterEncoding("UTF-8");
        res.setContentType("application/text;charset=utf-8");
        //1、必填token
        if(StringUtils.isEmpty(token)){
            logger.info("登陆成功后访问,url={},token为空"+req.getRequestURI());
            res.getWriter().write("token为空");
            return;
        }
        //2、校验token是否合法,合法则解析出userName
        //token可能是前端的,也可能是open api的
        String userName = JwtUtil.getUserNameByToken(req,token);
        if(StringUtils.isEmpty(userName)){
            logger.info("登陆成功后访问,url={},token错误或失效"+req.getRequestURI());
            res.getWriter().write("token错误或者失效");
            return;
        }
        //3、根据userName获取user实体,存入全局
        UserDetails currentUser = userDetailsService.loadUserByUsername(userName);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(currentUser,null,currentUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }
}
 6、一些dto和常量
package com.demo.security.dto;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ResponseMsg {
    private Object data;
    private Integer code;
}
package com.demo.security.constant;

import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.List;

public class UrlConstants {

    public static final String WEB_API_PRE = "/v1/**";
    public static final String OPEN_API_PRE = "/openApi/**";

    public static final List<AntPathRequestMatcher> WEB_MATCHERS =  List.of(
            new AntPathRequestMatcher(WEB_API_PRE));

    public static final List<AntPathRequestMatcher> OPEN_API_MATCHERS =
            List.of(
                    new AntPathRequestMatcher(OPEN_API_PRE));
}
 7、JWTUtil
package com.demo.security.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.demo.security.constant.UrlConstants;
import com.demo.security.dto.UserDTO;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Slf4j
public class JwtUtil {

    //签发方
    private static final String APPLICATION_ISSUER = "jwt_demo";
    //签发给
    private static final String TO_WEB = "to_web";
    private static final String TO_OPEN_API = "to_open_api";
    //密钥
    private static final String WEB_SECRET = "web-secret";
    private static final String OPEN_API_SECRET = "open-api-secret";
    //jwt过期事件
    public static final int EXPIRE_TIME = 30 * 60 * 1000;


    /**
     * 生成给前端的签名
     * @return 加密的token
     */
    public static String generateWebToken(UserDTO userDTO) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(WEB_SECRET);
        // 附带username信息
        return JWT.create()
                //iss:签发方
                .withIssuer(APPLICATION_ISSUER)
                //aud:接收jwt的一方
                .withAudience(TO_WEB)
                //exp:jwt的过期时间,这个过期时间必须要大于签发时间
                //.withExpiresAt()
                //其他自定义通信信息
                .withClaim("userName", userDTO.getUsername())
                //.withClaim("age",userDTO.getAge());
                .withExpiresAt(date)
                .sign(algorithm);
    }

    /**
     * open api的签名生成
     * 此处是给单元测试用的,调用方项目应该自己生成
     * @return 加密的token
     */
    public static String generateOpenApiToken(String userName) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(OPEN_API_SECRET);
        // 附带username信息
        return JWT.create()
                //iss:签发方
                .withIssuer(APPLICATION_ISSUER)
                //aud:接收jwt的一方
                .withAudience(TO_OPEN_API)
                //exp:jwt的过期时间,这个过期时间必须要大于签发时间
                //.withExpiresAt()
                //其他自定义通信信息
                .withClaim("userName", userName)
                //.withClaim("age",age);
                .withExpiresAt(date)
                .sign(algorithm);
    }

    public static String getUserNameByToken(HttpServletRequest req, String token) {
        DecodedJWT jwt = JWT.decode(token);
        //1、是否过期
        Date expireDate = jwt.getExpiresAt();
        if(expireDate.before(new Date())){
            log.error("token已过期");
            return null;
        }
        //2、签发方是否正确
        if(!APPLICATION_ISSUER.equals(jwt.getIssuer())){
            log.error("不是本项目签发的token");
            return null;
        }
        //3、是否合法
        String audience = jwt.getAudience().get(0);
        boolean check = preCheckToken(audience,token,req);
        if(!check){
            return null;
        }
        Map<String, Claim> claims = jwt.getClaims();
        String userName = claims.get("userName").asString();
        return userName;
    }

    /**
     * 根据url来判断是web还是open api更准确,
     * 如果只根据audience来判断,则web的token也可以访问open api;
     *
     * @param audience
     * @param token
     * @param req
     * @return
     */
    private static boolean preCheckToken(String audience, String token, HttpServletRequest req) {
        if(TO_WEB.equals(audience) && urlMatches(req, UrlConstants.WEB_MATCHERS)){
            log.info("这是前端token");
            return verifyToken(WEB_SECRET,token);
        }else if(TO_OPEN_API.equals(audience) && urlMatches(req,UrlConstants.OPEN_API_MATCHERS)){
            log.info("这是open api token");
            return verifyToken(OPEN_API_SECRET,token);
        }
        log.error("token来源不合法");
        return false;
    }


    public static boolean urlMatches(HttpServletRequest request, List<AntPathRequestMatcher> matchers) {
        Optional<AntPathRequestMatcher> first = matchers.stream().filter(m -> m.matches(request)).findFirst();
        return first.isPresent();
    }

    private static boolean verifyToken(String secret, String token) {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        try{
            JWTVerifier verifier = JWT.require(algorithm).build();
            verifier.verify(token);
            return true;
        }catch (Exception e){
            log.error("token非法");
            return false;
        }
    }
}
8、checkUtil
package com.demo.security.check;

import com.demo.security.dto.UserDTO;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.awt.*;
import java.util.List;

@Component("menuAuthorizationCheck")
public class MenuAuthorizationCheck {

    public boolean hasMenuAuthorization(String menuCode) {
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        List<String> menus = currentUser.getMenus();
        return menus.contains(menuCode);
    }

   /* *//**
     * open api是否有权限访问
     * @param menuCode
     * @return
     *//*
    public boolean hasOpenMenuAuthorization(String menuCode) {
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        List<String> menus = currentUser.getMenus();
        return menus.contains(menuCode);
    }*/
}
 9、controller

(1)前端业务接口

package com.demo.security.controller;

import com.demo.security.dto.ResponseMsg;
import com.demo.security.dto.UserDTO;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RequestMapping("/v1/user")
@RestController
public class UserController {

    @RequestMapping("/test")
    public String test(){
        return "这是user test";
    }

    @RequestMapping("/getCurrentUser")
    public ResponseMsg getCurrentUser() {
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println("当前用户为:"+currentUser);
        return ResponseMsg.builder().code(200).data(currentUser).build();
    }
}


package com.demo.security.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/v1/menu")
@RestController
public class MenuManageController {

    @PreAuthorize("@menuAuthorizationCheck.hasMenuAuthorization('menu_manage')")
    @RequestMapping("/test")
    public String test(){
        return "这是菜单管理";
    }
}

package com.demo.security.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/v1/school")
@RestController
@Slf4j
public class SchoolManageController {

    @RequestMapping("/test")
    public String test(){
        log.info("这是学校管理controller");
        return "这是学校管理";
    }
}
package com.demo.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/v1/role")
@RestController
public class RoleManageController {
    @RequestMapping("/test")
    public String test(){
        return "这是角色管理";
    }
}

(2)open api业务接口

package com.demo.security.openapi;


import com.demo.security.dto.ResponseMsg;
import com.demo.security.dto.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RequestMapping("/openApi/user")
@RestController
public class OpenUserController {

    @RequestMapping("/test")
    public String test(){
        return "这是open api user test";
    }

    @RequestMapping("/getCurrentUser")
    public ResponseMsg getCurrentUser() {
        UserDTO currentUser = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println("当前用户为:"+currentUser);
        return ResponseMsg.builder().code(200).data(currentUser).build();
    }
}


package com.demo.security.openapi;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/openApi/menu")
@RestController
public class OpenMenuController {

    @PreAuthorize("@menuAuthorizationCheck.hasMenuAuthorization('menu_manage')")
    @RequestMapping("/test")
    public String test(){
        return "这是open api菜单管理";
    }
}
package com.demo.security.openapi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/openApi/school")
@RestController
@Slf4j
public class OpenSchoolController {

    @RequestMapping("/test")
    public String test(){
        log.info("这是学校管理controller");
        return "这是open api学校管理";
    }
}

测试验证
(1)模拟前端调用

① 登录,访问 localhost:6666/jwtDemo/login?userName=admin&passWord=123

     

使用返回的token调用业务接口:

② 访问 localhost:6666/jwtDemo/v1/user/test

③ 访问localhost:6666/jwtDemo/v1/user/getCurrentUser

④ 访问localhost:6666/jwtDemo/v1/menu/test

⑤ 访问 localhost:6666/jwtDemo/v1/school/test

(2)模拟openapi调用

① 使用单元测试生成一个token

package com.demo.jwt.openapi;

import com.demo.security.JWTApplication;
import com.demo.security.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = {JWTApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
@Slf4j
public class MyTest {

    /**
     * 模拟open api调用方生成token。
     * open api应该自己生成token,双方约定好生成的算法、密钥和payload通信字段;
     * 不能调用本项目(被调用方)接口生成,否则对于生成token的接口需要加白名单,会造成接口攻击和token泄露的安全问题
     *
     */
    @Test
    public void  createOpenApiToken(){
        String userName = "zs";
        String token = JwtUtil.generateOpenApiToken(userName);
        log.info("生成token:{}",token);
    }

}

使用这个token调用业务接口:

② 访问 localhost:6666/jwtDemo/openApi/user/getCurrentUser

③ 访问 localhost:6666/jwtDemo/openApi/school/test

④ 访问 localhost:6666/jwtDemo/openApi/menu/test

(3)前端使用openapi的token调用接口

都报错

(4)openapi使用前端的token调用接口

同样都报错

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springBoot --> 学习笔记

文章目录 认识 SpringBoot第一个 SpringBoot 程序彩蛋 banner &#xff08;emmmmm&#xff0c;哈哈哈哈哈哈&#xff0c;牛逼&#xff01;&#xff09;SpringBoot 配置配置文件第一个 yaml 配置 成功案例yaml 存在 松散绑定 JSR 303 数据校验多环境配置以及文件位置访问静态资源…

教你制作一个二维码就能查分的系统

学生和家长对于成绩查询的需求日益增长。为了满足这一需求&#xff0c;很多学校和老师开始使用二维码查询系统&#xff0c;以提高效率和保护隐私。以下内容就是如何制作一个简单易用的成绩查询二维码系统的步骤&#xff1a; 1. 准备电子表格 老师需要准备一个包含学生成绩的电…

(已解决)vscode如何传入argparse参数来调试/运行python程序

文章目录 前言调试传入参数运行传入参数延申 前言 以前&#xff0c;我都是用Pycharm专业版的&#xff0c;由于其好像在外网的时候&#xff0c;不能够通过VPN来连接内网服务器&#xff0c;我就改用了vscode。改用了之后&#xff0c;遇到一个问题&#xff0c;调试或者运行python…

基于Qt5.12.2开发 MQTT客户端调试助手

项目介绍 该项目是一个基于 Qt 框架开发的桌面应用程序&#xff0c;主要用于与 MQTT 服务器进行连接和通信。通过该应用&#xff0c;用户可以连接到 MQTT 服务器&#xff0c;订阅主题、发布消息并处理接收到的消息。项目使用 QMqttClient 类来实现 MQTT 协议的客户端功能&…

第128集《大佛顶首楞严经》

《大佛顶如来密因修正了义诸菩萨万行首楞严经》。监院法师慈悲&#xff0c;诸位法师&#xff0c;诸位同学&#xff0c;阿弥陀佛&#xff01; 请大家打开讲义296面。 庚一、总示阴相&#xff08;分四&#xff1a;辛一、结前行阴尽相。辛二、正明识阴区宇。辛三、悬示识阴尽相。…

通过frp 免费内网穿透,端口转发

1.准备工作 (1)拥有一台有公网IP的服务器(系统可以是windows/macos/linux),服务器可以使用云厂商购买的服务器 (2)从下面链接下载最新版本的frp安装包,客户端和服务端是同一个tar包 https://github.com/fatedier/frp/releases 服务端机器A-有外网ip的作为服务端 服务端机器B-需…

前端接口415状态码【解决】

前端接口415状态码【解决】 一、概述 415状态码是HTTP协议中的一个标准响应状态码&#xff0c;代表“Unsupported Media Type”&#xff08;不支持的媒体类型&#xff09;。当客户端尝试上传或发送一个服务器无法处理的媒体类型时&#xff0c;服务器会返回这个状态码。这通常意…

二维四边形网格生成算法:paving(五)缝合 Seaming 与 闭合检测 Closure Check

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 参考论文&#xff1a;Paving: A new approach to automated quadrilateral mesh generation 关注公众号回复paving可以获得文章链接 paving&#xff08;一&#xff0…

python如何将字符转换为数字

python中的字符数字之间的转换函数 int(x [,base ]) 将x转换为一个整数 long(x [,base ]) 将x转换为一个长整数 float(x ) 将x转换到一个浮点数 complex(real [,imag ]) 创建一个复数 str(x ) 将对象 x 转换为字…

Pytest测试实战|执行常用命令

Pytest测试实战 本文章主要详细地阐述下Pytest测试框架执行TestCase常用命令。 按分类执行 在Pytest测试框架中按照分类执行的命令为“-k”&#xff0c;它的主要特点是按照TestCase名字的模式来执行&#xff0c;在编写具体的TestCase的时候&#xff0c;都会编写每个TestCase…

el-table表格点击该行任意位置时也勾选上其前面的复选框

需求&#xff1a;当双击表格某一行任意位置时&#xff0c;自动勾选上其前面的复选框 1、在el-table 组件的每一行添加row-dblclick事件&#xff0c;用于双击点击 <el-table:data"tableData"ref"tableRef"selection-change"handleSelectionChange&q…

常用组件详解(五):torch.nn.BatchNorm2d()

文章目录 一、基本原理二、函数说明 在卷积神经网络的卷积层之后通常会添加torch.nn.BatchNorm2d()进行数据的归一化处理&#xff0c;将数据规范到均值为0&#xff0c;方差为一的分布上&#xff0c;使得数据在进行Relu时不会因为数据过大而导致网络性能的不稳定。 一、基本原理…

基础实践:使用JQuery Ajax调用Servlet

前言 本博客介绍最简单的JQuery&#xff08;原生JS的封装库&#xff09;使用Ajax发送请求&#xff0c;并通过对应的servlet响应数据&#xff0c;并在页面显示&#xff0c;并且servlet响应的数据来自MySQL数据库。 实现需求&#xff1a;在前端页面的输入框中输入要注册的用户名&…

2024年_ChatGPT 及类似的人工智能技术带来的影响与改变 怎样利用 ChatGPT 提高学习效率

人工智能技术给我们带来了什么的改变&#xff1a; 工作方式与效率&#xff1a; 信息检索与处理&#xff1a;能快速整合大量信息&#xff0c;提供较为准确的答案和建议&#xff0c;帮助人们更高效地获取所需知识&#xff0c;提升信息检索和处理的速度与质量&#xff0c;比如在做…

Git版本控制的使用教程

使用Git进行项目代码的版本控制&#xff0c;具体操作&#xff1a; 1). 创建Git本地仓库 当Idea中出现&#xff1a; 说明本地仓库创建成功。 2). 创建Git远程仓库 访问Gitee - 基于 Git 的代码托管和研发协作平台&#xff0c;新建仓库 点击 创建 3). 将本地文件推送到Git远程…

Java【根据数据库生成实体文件】

下载插件 安装 MybatisX 配置包 Scratches and Consoles -> Extensions&#xff0c;Reload from Disk 后&#xff0c;会出现 MyBatisX 文件夹&#xff0c;将模板配置文件夹&#xff08;我的模板配置文件夹叫做 a-custom&#xff09;放入该文件夹下的 templates 文件夹内&am…

【欧拉函数变化】[SDOI2012] Longge 的问题

求和 gcd(i,j) 转化为 k*gcd(i/k,j/k) 1 (i,j%k 0)。 本质就是利用互质转化到了欧拉函数的领域上。 [SDOI2012] Longge 的问题 - 洛谷 转自小粉兔 #include<bits/stdc.h> #define int long long using namespace std; unordered_map<int,int>pd; long long…

天润融通发布微藤智能体平台,中国客户联络正式进入“智能体时代”

9月19日&#xff0c;以“云启智跃&#xff0c;产业蝶变”为主题的2024云栖大会在杭州正式开幕。大会持续三天&#xff0c;聚焦AI时代的技术升级与实践应用&#xff0c;设有三大主论坛、400多个分论坛&#xff0c;并开放4万平方米的智能科技展区&#xff0c;展示全球百余款AI应用…

20_BERT微调训练

1.导包 import json #通过路径加载预训练模型 import os import torch from torch import nn import dltools2.加载预训练模型BERT函数 def load_pretrained_model(pretrained_model, num_hiddens, ffn_num_hiddens,num_heads, num_layers, dropout, max_len, devices):dat…

David律所代理Jose Martin幽默水果版权首发维权,尚未TRO

案件基本情况&#xff1a;起诉时间&#xff1a;2024/9/18案件号&#xff1a;2024-cv-08484原告&#xff1a;Jose Martin原告律所&#xff1a;David起诉地&#xff1a;伊利诺伊州北部法院涉案商标/版权&#xff1a;原告品牌简介&#xff1a;西班牙的卓越艺术家Jose Martin以他非…