OAuth2+SpringSecurity实现鉴权认证

2,074 阅读6分钟

​本文已参与「新人创作礼」活动,一起开启掘金创作之路。​

一. 概述

OAuth2的四种授权模式

  • 授权码模式:这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

  • 简化模式:简化模式相对于授权码模式省略了,提供授权码,然后通过服务端发送授权码换取AccessToken的过程。一般简化模式用于没有服务器端的第三方单页面应用,因为没有服务器端就无法使用授权码模式。

  • 密码模式:密码模式是用户直接将自己的用户名密码交给client,client用用户的用户名密码直接换取AccessToken。这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。

  • 凭证式模式:这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与。

OAuth2几种授权方式的应用场景

  1. 授权码模式:第三方Web服务器端应用与第三方原生App

  2. 简化模式:第三方单页面应用

  3. 密码模式:第一方单页应用与第一方原生App

  4. 客户端模式:没有用户参与的,完全信任的服务器端服务

二. 本篇主要内容

依托SpringSecurity安全体系,整合Oauth2认证授权搭建的一套比较完善的安全认证框架。本文主要以密码模式进行代码梳理,其中扩展了手机短信验证模式。下面我们将介绍主要代码。
1.配置web安全配置

新建 WebSecurityConfig.java ,主要作用为web安全配置文件,主要注解为 @Configuration (配置文件注解) @EnableWebSecurity (开启web安全注解),继承 WebSecurityConfigurerAdapter 类,重写 authenticationManagerBean()configure()方法。 其中 authenticationManagerBean()方法主要是覆盖springboot自动创建的AuthenticationManager,防止冲掉内存中的用户. configure() 方法主要是进行第一步的认证拦截,类似于白名单,配置的不拦截,其他都拦截。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 这一步的配置是必不可少的,否则SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 放行认证路径 同时拦截所有路径需要授权
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //此处配置/oauth/** 放行认证获取token
        http.authorizeRequests().antMatchers("/oauth/**","/v1/**").permitAll()
                .anyRequest().authenticated().and()
                .httpBasic().and()
                .csrf().disable();
    }
}
2.配置认证文件

新建 AuthorizationServerConfig.java,该文件是认证的核心文件,继承 AuthorizationServerConfigurerAdapter 类,主要注解 @Configuration @EnableAuthorizationServer (开启认证服务)

2.1 重写 configure(AuthorizationServerSecurityConfigurer security) 方法,允许表单验证 防止报错。

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }

2.2 重写 configure(AuthorizationServerSecurityConfigurer security) 方法,客户端配置。有两种实现方式,其一、直接在代码中写死。 其二、动态拉取配置。由于系统过于复杂,不便于维护,本文采用从数据库中动态获取客户配置。 主要字段说明:

字段名详细说明
clientId客户端id
resourceIds资源ids
scope授权范围
authorizedGrantTypes认证类型
authorities权力,可以理解为对应得角色
webServerRedirectUriweb回调跳转url
clientSecret客户端密钥
accessTokenValiditySecondstoken验证有效时间
refreshTokenValiditySeconds刷新token有效时间

2.2.1 新建ClientDetailsServiceImpl.java 获取动态客户端配置,实现 ClientDetailsService 类中的 loadClientByClientId()方法。 主要参数没有实际调取数据库,可自行调取。测试直接静态量。

@Service
public class ClientDetailsServiceImpl implements ClientDetailsService {

    private String ClientId = "xxxxx";
    private String ResourceIds = "";
    private String Scope = "all";
    private String AuthorizedGrantTypes = "authorization_code,password,refresh_token,implicit,SMS"; // 若有自定义认证类型 此处需要添加自定义类型
        private String  Authorities = "";
    private String WebServerRedirectUri = "";
    private String ClientSecret = "123456";
    private int AccessTokenValiditySeconds = 3600*24;
    private int RefreshTokenValiditySeconds = 3600*24*7;

    @Override
    @SneakyThrows
    public ClientDetails loadClientByClientId(String clientId) {

        BaseClientDetails clientDetails = new BaseClientDetails(
                ClientId,
                ResourceIds,
                Scope,
                AuthorizedGrantTypes,
                Authorities,
                null);
        clientDetails.setClientSecret("{noop}" + ClientSecret); // 代表前端传入不加密
        return clientDetails;
    }
}

同时在AuthorizationServerConfig文件中配置客户端

    @Resource
    private ClientDetailsServiceImpl clientDetailsServiceImpl;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //super.configure(clients);

        // 配置客户端方式1 动态拉取client
        clients.withClientDetails(clientDetailsServiceImpl);

        // 配置客户端方式2 静态配置client
        //  clients.inMemory().withClient("client_1") // 客户端名称
        //                    .resourceIds(DEMO_RESOURCE_ID) // 资源id
        //                    .authorizedGrantTypes("password", "refresh_token") //授权类型 如果自定义授权类型 此处需要添加 传入tokengranter做后的参数 type
        //                    .authorities("oauth2")  // 手动添加角色
        //                    .scopes("all") // 授权范围
        //                    .secret("123456"); // 秘钥
        //                    .accessTokenValiditySeconds(ACCESS_TOKEN_TIMER) // token 验证过期
        //                    .refreshTokenValiditySeconds(REFRESH_TOKEN_TIMER); // 刷新token时间
    }

2.2.2 新建UserOauthVo.java ,实现 UserDetails

@Data
@NoArgsConstructor
public class UserOauthVo implements UserDetails {

    private Long userId;
    private String username;
    private String password;
    private Boolean enabled;

    private Collection<SimpleGrantedAuthority> authorities;

    public UserOauthVo(Long userId, String username, String password, Boolean enabled, Collection<SimpleGrantedAuthority> authorities) {
        this.setUserId(userId);
        this.setUsername(username);
        this.setPassword("{bcrypt}" + password);
        this.setEnabled(true);
        this.setAuthorities(authorities);
    }

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

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

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

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

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

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

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

2.2.3 新建UserDetailsServiceImpl.java ,实现 UserDetailsService类中的 **loadUserByUsername()**方法,主要实现自己的用户逻辑业务

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //TODO 此处应该是动态获取 目前做测试 先写死
        return new UserOauthVo(1l, "admin", "$2a$10$yJSqqr6sTxNuYtA6EKcVUe2I4USFCzJ29sNcRrBvtAkSYcNg5ydQ6", true, new ArrayList<>());
    }
}

2.2.4 配置认证提供器 在 AuthorizationServerConfig 文件中,创建Bean


    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private RedisConnectionFactory redisConnectionFactory;

    @Resource
    private UserDetailsServiceImpl userDetailsServiceImpl;
    /**
     * 认证提供器
     */
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 用户不存在异常抛出
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userDetailsServiceImpl);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    /**
     * 密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

2.2.5 认证端点配置

在 AuthorizationServerConfig 文件中,重写 configure(AuthorizationServerEndpointsConfigurer endpoints)方法

    /**
     * 端点配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // super.configure(endpoints);
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancerList = new ArrayList<>();
        tokenEnhancerList.add(tokenEnhancer());
        tokenEnhancerList.add(accessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancerList);

        endpoints.tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .accessTokenConverter(accessTokenConverter())
                .userDetailsService(userDetailsServiceImpl)
                // 自定义的TokenGranter,需要打开 否则关闭即可
                .tokenGranter(this.getDefaultTokenGranters(endpoints))
                .tokenStore(tokenStore())
                // refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
                //      1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
                //      2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的
                .reuseRefreshTokens(true)
                // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

    /**
     * JWT 生成token 定制处理
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            Map<String, Object> additionalInfo = new HashMap();
            UserOauthVo userOauthVo = (UserOauthVo) authentication.getUserAuthentication().getPrincipal();
            additionalInfo.put("userId", userOauthVo.getUserId());
            additionalInfo.put("username", userOauthVo.getUsername());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        };
    }

    /**
     * JWT 加密
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        // 方式一:采用公钥+私钥
        // jwtAccessTokenConverter.setKeyPair(keyPair());
        // 方式二: 直接写死
        jwtAccessTokenConverter.setSigningKey("abcd123456");
        return jwtAccessTokenConverter;
    }

    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }

    /**
     * 使用redis 存取token
     */
    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        return tokenStore;
    }

2.2.6 扩展密码模式--短信验证码认证 1.创建自定义类型逻辑业务文件 SMSCodeTokenGranter.java, 继承AbstractTokenGranter,重写 **getOAuth2Authentication()**方法

public class SMSCodeTokenGranter extends AbstractTokenGranter {

   public SMSCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
       super(tokenServices, clientDetailsService, requestFactory, grantType);
   }

   @Override
   protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
       // return super.getOAuth2Authentication(client, tokenRequest);
       LinkedHashMap<String, String> parameters  = new LinkedHashMap<>(tokenRequest.getRequestParameters());
       // 可以获取到传入的参数
       String userMobileNo = parameters.get("mobile");  //客户端提交的用户名
       String smsCode = parameters.get("smscode");  //客户端提交的验证码

       // TODO 写自己的验证逻辑
       UserOauthVo userOauthVo = new UserOauthVo(
               1l,
               "admin",
               "$2a$10$yJSqqr6sTxNuYtA6EKcVUe2I4USFCzJ29sNcRrBvtAkSYcNg5ydQ6",
               true, new ArrayList<>());

       Authentication userAuth = new UsernamePasswordAuthenticationToken(userOauthVo, null, userOauthVo.getAuthorities());
       ((AbstractAuthenticationToken) userAuth).setDetails(parameters);

       OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
       return new OAuth2Authentication(storedOAuth2Request, userAuth);
   }
}
、

2.创建SMSCodeAuthenticationToken.java 实现自己的Token存储机制, 继承 AbstractAuthenticationToken

public class SMSCodeAuthenticationToken extends AbstractAuthenticationToken {
   private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

   private final Object principal;

   private Object credentials;



   public SMSCodeAuthenticationToken(String mobile, String password) {
       super(null);
       this.principal = mobile;
       this.credentials = password;
       setAuthenticated(false);
   }

   public SMSCodeAuthenticationToken(Object principal, Object credentials,
                                    Collection<? extends GrantedAuthority> authorities) {
       super(authorities);
       this.principal = principal;
       this.credentials = credentials;
       super.setAuthenticated(true);
   }


   @Override
   public Object getCredentials() {
       return this.credentials;
   }

   @Override
   public Object getPrincipal() {
       return this.principal;
   }

   @Override
   public void setAuthenticated(boolean isAuthenticated) {
       if (isAuthenticated) {
           throw new IllegalArgumentException(
                   "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
       }
       super.setAuthenticated(false);
   }

   @Override
   public void eraseCredentials() {
       super.eraseCredentials();
   }

3.创建自己的认证处理器 SMSCodeAuthenticationProvider.java ,实现 AuthenticationProvider,重写 authenticate() 方法。

@Component
public class SMSCodeAuthenticationProvider implements AuthenticationProvider {


   @Override
   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
       SMSCodeAuthenticationToken authToken = (SMSCodeAuthenticationToken) authentication;
       //调用userdetailService获取认证信息(按自己的业务实现)返回封装好的SysAuthUser

       UserOauthVo userOauthVo = new UserOauthVo(1l, "admin", "$2a$10$yJSqqr6sTxNuYtA6EKcVUe2I4USFCzJ29sNcRrBvtAkSYcNg5ydQ6", true, new ArrayList<>());
       //认证成功后构造一个新的AuthenticationToken,传入认证好的用户信息和权限信息等
       SMSCodeAuthenticationToken authenticationResult = new SMSCodeAuthenticationToken(userOauthVo, userOauthVo.getPassword(), userOauthVo.getAuthorities());
       authenticationResult.setDetails(authToken.getDetails());
       return authenticationResult;
   }

   @Override
   public boolean supports(Class<?> authentication) {
       return SMSCodeAuthenticationToken.class.isAssignableFrom(authentication);
   }
}

4.自定义文件完成后需要在 WebSecurityConfig .java文件中配置提供的认证处理器

 @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.authenticationProvider(smsCodeAuthenticationProvider);
   }

5.在 AuthorizationServerConfig 文件中,添加自己的认证类型

    /**
     * 自定义认证类型
     */
    private TokenGranter getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
        // 获取原有默认的授权类型
        ArrayList<TokenGranter> tokenGranters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
        // 创建自定义认证类型
        SMSCodeTokenGranter sMSCodeTokenGranter = new SMSCodeTokenGranter(
                endpoints.getTokenServices(),
                endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(),
                "SMS"
        );
        // 将自定义认证类型加入到原有的认证集合中 返回
        tokenGranters.add(sMSCodeTokenGranter);
        return new CompositeTokenGranter(tokenGranters);
    }

需注意:"SMS"为自定义的认证类型,需要在ClientDetailsServiceImpl文件中AuthorizedGrantTypes增加类型" SMS"

3.配置资源文件

新建ResourceServerConfig.java,继承 ResourceServerConfigurerAdapter 类,重写 configure(ResourceServerSecurityConfigurer resources)void configure(HttpSecurity http),主要注解@Configuration @EnableResourceServer(开启资源服务)

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Resource
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        // 由于认证配置文件中配置了存储方式为redis ,此处必须配置,可直接注入Bean使用
        resources.stateless(true).tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().antMatchers("/oauth/**", "/v1/**").permitAll() // 配置不拦截 不需要token 直接访问
                .and()
                .authorizeRequests().anyRequest().authenticated();
    }
}

####4.访问接口获取认证token

url:/oauth/token method:POST param:grant_type=password&username=admin&password=123456&scope=all heade中的设置: Authorization:Basic iE5ALkdUaqU4Dxl6s==

注意:iE5ALkdUaqU4Dxl6s== 是 client_id:client_secret Base64后加密得到的,且访问/oauth/token时,Authorization值中 必须带有Basic标识

@Api(tags = "认证中心")
@RestController
@RequestMapping("/oauth")
@Slf4j
public class OauthController {

    @Resource
    private TokenEndpoint tokenEndpoint;

    @ApiOperation(value = "OAuth2认证", notes = "login")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "grant_type", defaultValue = "password", value = "授权模式", required = true),
            @ApiImplicitParam(name = "client_id", value = "Oauth2客户端ID(新版本需放置请求头)", required = true),
            @ApiImplicitParam(name = "client_secret", value = "Oauth2客户端秘钥(新版本需放置请求头)", required = true),
            @ApiImplicitParam(name = "refresh_token", value = "刷新token"),
            @ApiImplicitParam(name = "username", defaultValue = "admin", value = "登录用户名"),
            @ApiImplicitParam(name = "password", defaultValue = "123456", value = "登录密码"),
            @ApiImplicitParam(name = "phone", defaultValue = "13888888888", value = "手机号"),
            @ApiImplicitParam(name = "smsCode", defaultValue = "000000", value = "验证码")
    })
    @PostMapping("/token")
    public Object postAccessToken(
            @ApiIgnore Principal principal,
            @ApiIgnore @RequestParam Map<String, String> parameters
    ) throws HttpRequestMethodNotSupportedException {
        return tokenEndpoint.postAccessToken(principal, parameters).getBody();
    }
}

正常返回结果

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjY0Mzk5ODksInVzZXJJZCI6MSwianRpIjoiMWY1YjNjOWUtOGU0NS00MGZhLThlZjMtNmQwMGFkZjc3MTNkIiwiY2xpZW50X2lkIjoieW91bGFpLWFkbWluIiwidXNlcm5hbWUiOiJhZG1pbiJ9.xulwv6DduTDppZzvWXDZUzxfWmmeVyzKNdoFH8lcpF4",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiIxZjViM2M5ZS04ZTQ1LTQwZmEtOGVmMy02ZDAwYWRmNzcxM2QiLCJleHAiOjE2Mjg5ODg3ODksInVzZXJJZCI6MSwianRpIjoiMDU3ZDQwM2QtNGYzZC00Y2NhLTk5M2UtMDQyZGVhZmM5MTFlIiwiY2xpZW50X2lkIjoieW91bGFpLWFkbWluIiwidXNlcm5hbWUiOiJhZG1pbiJ9.civ4NI38Bgz5F_XnE5wXn366EK-45sPkeuCCAnHVplc",
    "expires_in": 42956,
    "scope": "all",
    "userId": 1,
    "username": "admin",
    "jti": "1f5b3c9e-8e45-40fa-8ef3-6d00adf7713d"
}

访问其他请求时在header中添加 Authorization:bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjY0Mzk5ODksInVzZXJJZCI6MSwianRpIjoiMWY1YjNjOWUtOGU0NS00MGZhLThlZjMtNmQwMGFkZjc3MTNkIiwiY2xpZW50X2lkIjoieW91bGFpLWFkbWluIiwidXNlcm5hbWUiOiJhZG1pbiJ9.xulwv6DduTDppZzvWXDZUzxfWmmeVyzKNdoFH8lcpF4 即可。 ####5.解析token

public static Claims parseJWT(String token) throws Exception {
        return (Claims) Jwts.parser()
                .setSigningKey("abcd123456".getBytes("utf-8"))
                .parseClaimsJws(token)
                .getBody();
    }
        token = token.replace("bearer ", Strings.EMPTY);
        Claims claims = JwtUtil.parseJWT(token);

####6.认证异常处理

@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum OauthConstant {

    USER_NOT_FOUND(20001, "用户不存在"),
    PASSWORD_OAUTH_FAIL(20002, "用户密码错误"),
    ACCOUNT_EXCEPTION(20003, "账号异常"),
    UNSUPPORT_OAUTH_TYPE(20004, "授权模式不支持"),;
    private Integer code;
    private String msg;
}
@RestControllerAdvice
public class OauthExceptionHandler {

    /**
     * 用户不存在
     */
    @ExceptionHandler(UsernameNotFoundException.class)
    public ResultModel handleUsernameNotFoundException(UsernameNotFoundException e) {
       return new ResultModel<>().error(OauthConstant.USER_NOT_FOUND.getCode(),OauthConstant.USER_NOT_FOUND.getMsg());
    }

    /**
     * 用户名和密码异常
     */
    @ExceptionHandler(InvalidGrantException.class)
    public ResultModel handleInvalidGrantException(InvalidGrantException e) {
        return new ResultModel<>().error(OauthConstant.PASSWORD_OAUTH_FAIL.getCode(),OauthConstant.PASSWORD_OAUTH_FAIL.getMsg());
    }

    /**
     * 账户异常(禁用、锁定、过期)
     */
    @ExceptionHandler({InternalAuthenticationServiceException.class})
    public ResultModel handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
        return new ResultModel<>().error(OauthConstant.ACCOUNT_EXCEPTION.getCode(),OauthConstant.ACCOUNT_EXCEPTION.getMsg());
    }

    /**
     * 授权模式不支持
     */
    @ExceptionHandler({UnsupportedGrantTypeException.class})
    public ResultModel handleUnsupportedGrantTypeException(UnsupportedGrantTypeException e) {
        return new ResultModel<>().error(OauthConstant.UNSUPPORT_OAUTH_TYPE.getCode(),OauthConstant.UNSUPPORT_OAUTH_TYPE.getMsg());
    }
}