SpringBoot+shiro+JwtToken,多方式登录

使用场景:用户使用手机号或密码登录后返回token,之后的请求的请求头带上token进行token鉴权。


 一、依赖

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

        <!-- JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>  
        
        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.verison}</version>
        </dependency>

二、配置

package com.ydzl.lxyd.config;


import com.ydzl.lxyd.config.realm.CodeRealm;
import com.ydzl.lxyd.config.realm.JwtRealm;
import com.ydzl.lxyd.config.realm.PasswordRealm;
import com.ydzl.lxyd.config.realm.UserModularRealmAuthenticator;
import com.ydzl.lxyd.filter.JwtFilter;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.*;

/**
 * @author lixiao
 * @date 2019/7/31 11:34
 */
@Configuration
public class ShiroConfig {

    /**
     * 开启shiro权限注解
     * @return DefaultAdvisorAutoProxyCreator
     */
    @Bean
    public static DefaultAdvisorAutoProxyCreator creator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }


    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager 安全管理器
     * @return AuthorizationAttributeSourceAdvisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * 密码登录时使用该匹配器进行匹配
     * @return HashedCredentialsMatcher
     */
    @Bean("hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // 设置哈希算法名称
        matcher.setHashAlgorithmName("MD5");
        // 设置哈希迭代次数
        matcher.setHashIterations(1024);
        // 设置存储凭证十六进制编码
        matcher.setStoredCredentialsHexEncoded(true);
        return matcher;
    }


    @Bean
    public PasswordRealm passwordRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
        PasswordRealm userRealm = new PasswordRealm();
        userRealm.setCredentialsMatcher(matcher);
        return userRealm;
    }

    @Bean
    public CodeRealm codeRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
        CodeRealm codeRealm = new CodeRealm();
        codeRealm.setCredentialsMatcher(matcher);
        return codeRealm;
    }

    @Bean
    public JwtRealm jwtRealm(){
        return new JwtRealm();
    }

    /**
     * Shiro内置过滤器,可以实现拦截器相关的拦截器
     *    常用的过滤器:
     *      anon:无需认证(登录)可以访问
     *      authc:必须认证才可以访问
     *      user:如果使用rememberMe的功能可以直接访问
     *      perms:该资源必须得到资源权限才可以访问
     *      role:该资源必须得到角色权限才可以访问
     **/
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置 SecurityManager
        bean.setSecurityManager(securityManager);
        // 设置未登录跳转url
        bean.setLoginUrl("/user/unLogin");

        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/**","anon");
        filterMap.put("/static/**","anon");
        filterMap.put("/user/logout", "logout");
        //从这里开始,是我为解决问题增加的,为swagger页面放行
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/v2/**", "anon");
        filterMap.put("/webjars/**", "anon");

        Map<String, Filter> filter = new HashMap<>(1);
        filter.put("jwt", new JwtFilter());
        bean.setFilters(filter);
        filterMap.put("/**", "jwt");

        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }


    @Bean
    public UserModularRealmAuthenticator userModularRealmAuthenticator(){
        //自己重写的ModularRealmAuthenticator
        UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

    /**
     *  SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
     */
    @Bean
    public SecurityManager securityManager(@Qualifier("passwordRealm") PasswordRealm passwordRealm, @Qualifier("codeRealm") CodeRealm codeRealm,
                                           @Qualifier("jwtRealm") JwtRealm jwtRealm,
                                           @Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm
        securityManager.setAuthenticator(userModularRealmAuthenticator);
        List<Realm> realms = new ArrayList<>();
        // 添加多个realm
        realms.add(passwordRealm);
        realms.add(codeRealm);
        realms.add(jwtRealm);
        securityManager.setRealms(realms);

        /*
         * 关闭shiro自带的session,详情见文档
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

}

三、shiro多方式鉴权需要配置多个realm

  1.验证码登录Realm

package com.ydzl.lxyd.config.realm;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.ydzl.lxyd.config.token.CustomizedToken;
import com.ydzl.lxyd.pojo.user.User;
import com.ydzl.lxyd.service.user.UserService;
import com.ydzl.lxyd.util.JwtUtil;
import com.ydzl.lxyd.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;

/**
 * @author lixiao
 * @date 2019/7/31 11:40
 */
@Slf4j
public class CodeRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Resource
    private RedisUtil redisUtil;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomizedToken;
    }

    /**
     * 获取授权信息
     * @param principals principals
     * @return AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 获取身份认证信息
     * @param authenticationToken authenticationToken
     * @return AuthenticationInfo
     * @throws AuthenticationException AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        CustomizedToken token = (CustomizedToken) authenticationToken;
        log.info("CodeRealm"+token.getUsername()+"开始身份认证");
        // 根据手机号查询用户
        User user = userService.selectOne(new EntityWrapper<User>().eq("phone", token.getUsername()));
        if (user == null) {
            // 抛出账号不存在异常
            throw new UnknownAccountException();
        }
        // 1.principal:认证的实体信息,可以是手机号,也可以是数据表对应的用户的实体类对象
        // 2.从redis中获取登录验证码
        Object credentials = redisUtil.get(user.getPhone()+"loginCode");
        // 3.realmName:当前realm对象的name,调用父类的getName()方法即可

        // 4.盐,取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getPhone());

        return new SimpleAuthenticationInfo(user, credentials, credentialsSalt, getName());
    }
}

  2.密码登录Realm

package com.ydzl.lxyd.config.realm;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.ydzl.lxyd.config.token.CustomizedToken;
import com.ydzl.lxyd.pojo.user.User;
import com.ydzl.lxyd.service.user.UserService;
import com.ydzl.lxyd.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;

/**
 * @author lixiao
 * @date 2019/7/31 11:40
 */
@Slf4j
public class PasswordRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomizedToken;
    }

    /**
     * 获取授权信息
     * @param principals principals
     * @return AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 获取身份认证信息
     * @param authenticationToken authenticationToken
     * @return AuthenticationInfo
     * @throws AuthenticationException AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        CustomizedToken token = (CustomizedToken) authenticationToken;
        log.info("PasswordRealm"+ token.getUsername()+ "开始身份认证");
        // 根据手机号查询用户
        User user = userService.selectOne(new EntityWrapper<User>().eq("phone", token.getUsername()));
        if (user == null) {
            // 抛出账号不存在异常
            throw new UnknownAccountException();
        }
        // 1.principal:认证的实体信息,可以是手机号,也可以是数据表对应的用户的实体类对象
        // 2.credentials:密码
        Object credentials = user.getPassword();
        // 3.realmName:当前realm对象的name,调用父类的getName()方法即可

        // 4.盐,取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getPhone());
        return new SimpleAuthenticationInfo(user, credentials, credentialsSalt, getName());
    }
}

  3.JwtTokenRealm

package com.ydzl.lxyd.config.realm;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.ydzl.lxyd.config.token.JwtToken;
import com.ydzl.lxyd.enums.ResultEnums;
import com.ydzl.lxyd.pojo.user.User;
import com.ydzl.lxyd.service.user.UserService;
import com.ydzl.lxyd.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;

/**
 * @author lixiao
 * @date 2019/8/6 10:02
 */
@Slf4j
public class JwtRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("JwtRealm  doGetAuthorizationInfo  运行()");
        String username = JwtUtil.getPhone(principals.toString());
        User user = userService.selectOne(new EntityWrapper<User>().eq("phone", username));

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRole(user.getLastLoginIdentity()==0?"user":"driver");
        return authorizationInfo;

    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        // 解密获得username,用于和数据库进行对比
        String username = JwtUtil.getPhone(token);
        if (username == null) {
            throw new AuthenticationException(ResultEnums.TOKEN_INVALID.getMsg());
        }
        User user = userService.selectOne(new EntityWrapper<User>().eq("phone", username));
        if(user == null){
            throw new AuthenticationException(ResultEnums.USER_NOT_EXIST.getMsg());
        }
        if (!JwtUtil.verify(token, username, user.getUserId(), user.getPassword())) {
            throw new AuthenticationException(ResultEnums.TOKEN_ERROR.getMsg());
        }
        return new SimpleAuthenticationInfo(token, token, "JwtRealm");
    }
}

  4.当配置了多个Realm时,我们通常使用的认证器是shiro自带的 org.apache.shiro.authc.pam.ModularRealmAuthenticator, 其中决定使用的Realm的是doAuthenticate()方法

package com.ydzl.lxyd.config.realm;

import com.ydzl.lxyd.config.token.CustomizedToken;
import com.ydzl.lxyd.config.token.JwtToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;

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

/**
 * @author lixiao
 * @date 2019/7/31 20:48
 *  当配置了多个Realm时,我们通常使用的认证器是shiro自带的
 *  org.apache.shiro.authc.pam.ModularRealmAuthenticator,
 *  其中决定使用的Realm的是doAuthenticate()方法
 */
@Slf4j
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("UserModularRealmAuthenticator:method doAuthenticate() 执行 ");
        // 判断getRealms()是否返回为空
        assertRealmsConfigured();

        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登录类型对应的所有Realm
        Collection<Realm> typeRealms = new ArrayList<>();

        // 强制转换回自定义的Token
        try{
            JwtToken jwtToken = (JwtToken) authenticationToken;
            for(Realm realm : realms){
                if (realm.getName().contains("Jwt")){
                    typeRealms.add(realm);
                }
            }
            return doSingleRealmAuthentication(typeRealms.iterator().next(), jwtToken);
        }catch (ClassCastException e){
            typeRealms.clear();

            CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
            // 登录类型
            String loginType = customizedToken.getLoginType();

            for (Realm realm : realms) {
                if (realm.getName().contains(loginType)){
                    typeRealms.add(realm);
                }
            }

            // 判断是单Realm还是多Realm
            if(typeRealms.size() == 1){
                return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
            }else {
                return doMultiRealmAuthentication(typeRealms, customizedToken);
            }
        }

    }
}

四、其中用到了JWTUtils / 自定义TOKEN

package com.ydzl.lxyd.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ydzl.lxyd.commons.Constant;

import java.util.Date;

/**
 * @author lixiao
 * @date 2019/8/5 23:46
 */
public class JwtUtil {


    /**
     * 校验token是否正确
     *
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String phone, Integer userId, String secret) {
        try {
            //根据密码生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("phone", phone)
                    .withClaim("userId", String.valueOf(userId))
                    .build();
            // 效验TOKEN
            verifier.verify(token);
            return true;
        } catch (JWTVerificationException exception) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getPhone(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("phone").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getUserId(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("userId").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     *
     * @param phone 用户名/手机号
     * @param userId   用户id
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String phone,Integer userId, String secret) {
        Date date = new Date(System.currentTimeMillis() + Constant.TOKEN_EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        return JWT.create()
                .withClaim("phone", phone)
                .withClaim("userId", String.valueOf(userId))
                .withExpiresAt(date)
                .sign(algorithm);

    }

}
package com.ydzl.lxyd.config.token;

import org.apache.shiro.authc.UsernamePasswordToken;

/**
 * @author lixiao
 * @date 2019/7/31 20:54
 */
public class CustomizedToken extends UsernamePasswordToken {

    /**
     * 登录类型
     */
    private String loginType;

    public CustomizedToken(final String username, final String password, String loginType){
        super(username, password);
        this.loginType = loginType;
    }

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }

    @Override
    public String toString(){
        return "loginType="+ loginType +",username=" + super.getUsername()+",password="+ String.valueOf(super.getPassword());
    }
}
package com.ydzl.lxyd.config.token;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author lixiao
 * @date 2019/8/5 23:43
 */
public class JwtToken implements AuthenticationToken {

    private String token;

    public JwtToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

六、自定义filter拦截自定义请求头拦截 / 获取token后进行鉴权

package com.ydzl.lxyd.filter;

import com.ydzl.lxyd.commons.Constant;
import com.ydzl.lxyd.config.token.JwtToken;

import com.ydzl.lxyd.enums.ResultEnums;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lixiao
 * @date 2019/8/5 23:46
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 执行登录认证
     * @param request ServletRequest
     * @param response ServletResponse
     * @param mappedValue mappedValue
     * @return 是否成功
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            executeLogin(request, response);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
 
    /**
     * 执行登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response){
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(Constant.TOKEN_HEADER_NAME);
        if (token == null) {
            try {
                request.getRequestDispatcher("/user/tokenError?msg="+ ResultEnums.TOKEN_NOT_EXIST.getMsg()).forward(request, response);
            } catch (ServletException | IOException e) {
                log.error("JwtFilter  executeLogin() /user/tokenError");
            }
            return false;
        }
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        try{
            getSubject(request, response).login(jwtToken);
        } catch (AuthenticationException e){
            try {
                request.getRequestDispatcher("/user/tokenError?msg="+e.getMessage()).forward(request, response);
            } catch (ServletException | IOException e1) {
                log.error("JwtFilter  executeLogin() /user/tokenError");
            }
        }
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }
 
 
 
    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

相关疑问请邮件联系:[email protected]

 

猜你喜欢

转载自www.cnblogs.com/knightdreams6/p/11355470.html