自定义的SpringSecurityOauth2(普通版)

430 阅读3分钟

公司项目调整, 想拥有一个自己的用户中心,类似盛大令牌这种。好家伙,需要一套自己的令牌,简单来说就是一个认证中心,负责颁发令牌。

因为暂时是内部系统使用,直接账号密码,手机号验证码,邮箱验证码登录等等。等自己项目做起来了第三方对接过来才考虑OAuth2协议。之前看过一个Sa-token的框架,写的挺简洁的,但是考虑了SpringSecurity还是稳(出了Bug好搜索)。最后决定使用SpringSecurityOAuth2。

使用jwt

主要代码()

1.1 UserDetailsService

这里存在一个大坑。

@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
  @Autowired private UserService userService;
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      //这里请求用户中心访问数据
      return userService.getUserByName(username);
  }
}

1.2 自定义的用户认证转换器(增强器)CustomUserAuthenticationConverter

@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {

  @Resource UserDetailsService userDetailsService;

  /**
   * 拓展jwt
   *
   * @param authentication
   * @return
   */
  @Override
  public Map<String, ?> convertUserAuthentication(Authentication authentication) {
    LinkedHashMap map = new LinkedHashMap();
    String name = authentication.getName();
    map.put("user_name", name);

    Object principal = authentication.getPrincipal();
    OAuthUserDetail userJwt = null;
    if (principal instanceof OAuthUserDetail) {
      userJwt = (OAuthUserDetail) principal;
    } else {
      // refresh_token默认不去调用userdetailService获取用户信息,这里我们手动去调用,得到 UserJwt
      UserDetails userDetails = userDetailsService.loadUserByUsername(name);
      userJwt = (OAuthUserDetail) userDetails;
    }
    map.put("phone", userJwt.getPhone());
    map.put("uid", userJwt.getId());
    map.put("appUserId", userJwt.getAppUserId());
    //    时间戳
    map.put("timestamp", System.currentTimeMillis());
    if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
      map.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
    }
    return map;
  }
}

1.3 OAuth2Config

/**
 * @author:longcheng
 * @date:2021/12/17 10:09
 * @version:1.0
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

  // 数据库连接池对象
  @Autowired
  private DataSource dataSource;
  // 认证业务对象
  @Autowired
  private UserDetailServiceImpl userService;

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  private OAuthServerAuthenticationEntryPoint authServerAuthenticationEntryPoint;

  @Autowired
  private CustomUserAuthenticationConverter authenticationConverter;

  @Autowired
  private KeyProperties keyProperties;


  // 客户端信息来源
  @Bean
  public JdbcClientDetailsService jdbcClientDetailsService() {
    return new JdbcClientDetailsService(dataSource);
  }

  @Bean
  public TokenStore tokenStore() {
    JwtTokenStore tokenStore = new JwtTokenStore(accessTokenConverter());
    return tokenStore;
  }

  // 授权码模式数据来源
  @Bean
  public AuthorizationCodeServices authorizationCodeServices() {
    return new JdbcAuthorizationCodeServices(dataSource);
  }

  // 授权信息保存策略
  @Bean
  public ApprovalStore approvalStore() {
    return new JdbcApprovalStore(dataSource);
  }

  //
  @Bean
  public CustomDefaultTokenServices customTokenServices() {
    CustomDefaultTokenServices tokenServices = new CustomDefaultTokenServices();
    tokenServices.setTokenStore(tokenStore());
    tokenServices.setSupportRefreshToken(true);
    tokenServices.setReuseRefreshToken(true);
    tokenServices.setAuthenticationManager(authenticationManager);
    tokenServices.setTokenEnhancer(accessTokenConverter());
    // access_token有效期:2个小时 -> 60*60*2
    tokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
    // refresh_token有效期:12个小时 -> 60*60*12
    tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 12);
    return tokenServices;
  }


  @Bean
  public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    KeyPair keyPair =
            new KeyStoreKeyFactory(
                    keyProperties.getKeyStore().getLocation(),
                    keyProperties.getKeyStore().getSecret().toCharArray())
                    .getKeyPair(
                            keyProperties.getKeyStore().getAlias(),
                            keyProperties.getKeyStore().getPassword().toCharArray());
    converter.setKeyPair(keyPair);
    // 配置自定义的CustomUserAuthenticationConverter
    DefaultAccessTokenConverter accessTokenConverter =
            (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
    accessTokenConverter.setUserTokenConverter(authenticationConverter);
    return converter;
  }

  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    //    ClientId、Client-Secret:这两个参数对应请求端定义的 cleint-id 和 client-secret
    clients.withClientDetails(jdbcClientDetailsService());
  }

  /**
   * 主配置信息
   *
   * @param endpoints
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints
//            指定服务异常翻译器
            .exceptionTranslator(new AuthResponseExceptionTranslator())
            .approvalStore(approvalStore())
            .authenticationManager(authenticationManager)
            .userDetailsService(userService)
            //授权码模式需要
            .authorizationCodeServices(authorizationCodeServices())
            // token 管理
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter())
            .tokenServices(customTokenServices());
  }

  // 检查token的策略
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    AuthClientCredentialsTokenEndpointFilter authClientCredentialsTokenEndpointFilter =
            new AuthClientCredentialsTokenEndpointFilter(security, authServerAuthenticationEntryPoint);
    authClientCredentialsTokenEndpointFilter.afterPropertiesSet();
    security.addTokenEndpointAuthenticationFilter(authClientCredentialsTokenEndpointFilter);
//        security.allowFormAuthenticationForClients();
//    security.authenticationEntryPoint(authServerAuthenticationEntryPoint);
    //    开启token认证权限
    security.tokenKeyAccess("permitAll");
    //    开启token校验
    security.checkTokenAccess("permitAll");
  }
}

1.4 异常翻译器

一看这么多if/else if。这里它自己也是这么写的。异常处理也有点麻烦,后续再说。

重写这个异常处理器是为了返回我自己要的格式。Result风格,根据code码来区分。

/**
 * ClientCredentialsTokenEndpointFilter
 *
 * @author:longcheng
 * @date:2021/12/20 13:54
 * @version:1.0 重写服务异常翻译器
 */
public class AuthResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {
    @Override
    public ResponseEntity translate(Exception e) throws Exception {

        return new ResponseEntity(doTranslateHandler(e), HttpStatus.UNAUTHORIZED);
    }
    /**
     * 错误类型
     *
     * @param e
     * @return
     */
    private R doTranslateHandler(Exception e) {
        if (e instanceof InvalidClientException) {
            return R.fail(AuthResultCode.INVALID_CLIENT);
        } else if (e instanceof UnauthorizedClientException) {
            return R.fail(AuthResultCode.UNAUTHORIZED_CLIENT);
        } else if (e instanceof InvalidGrantException) {
            return R.fail(AuthResultCode.INVALID_GRANT);
        } else if (e instanceof InvalidScopeException) {
            return R.fail(AuthResultCode.INVALID_SCOPE);
        } else if (e instanceof InvalidTokenException) {
            return R.fail(AuthResultCode.INVALID_TOKEN);
        } else if (e instanceof InvalidRequestException) {
            return R.fail(AuthResultCode.INVALID_REQUEST);
        } else if (e instanceof RedirectMismatchException) {
            return R.fail(AuthResultCode.REDIRECT_URI_MISMATCH);
        } else if (e instanceof UnsupportedGrantTypeException) {
            return R.fail(AuthResultCode.UNSUPPORTED_GRANT_TYPE);
        } else if (e instanceof UnsupportedResponseTypeException) {
            return R.fail(AuthResultCode.UNSUPPORTED_RESPONSE_TYPE);
        } else if (e instanceof UserDeniedAuthorizationException) {
            return R.fail(AuthResultCode.ACCESS_DENIED);
        }
        return R.fail(AuthResultCode.LOGIN_ERROR);
    }
}

1.4 OauthController

@RestController
@RequestMapping("/oauth")
@Api(tags = "token认证获取")
public class OauthController {

  @Autowired
  private TokenEndpoint tokenEndpoint;

  @Autowired
  private CheckTokenEndpoint checkTokenEndpoint;

  @PostMapping("/token")
  @ApiOperation("Post请求token")
  @ApiImplicitParams({
          @ApiImplicitParam(name = "grant_type", value = "认证类型", dataType = "String", required = true),
          @ApiImplicitParam(name = "client_id", value = "系统id", dataType = "String", required = true),
          @ApiImplicitParam(name = "client_secret", value = "系统密钥", dataType = "String", required = true),
          @ApiImplicitParam(name = "username", value = "账号", dataType = "String", required = false),
          @ApiImplicitParam(name = "password", value = "密码", dataType = "String", required = false),
          @ApiImplicitParam(name = "refresh_token", value = "刷新token", required = false),
  })
  public R<Object> postAccessToken(
          Principal principal, @RequestParam Map<String, String> parameters)
      throws HttpRequestMethodNotSupportedException {
    return R.data(tokenEndpoint.postAccessToken(principal, parameters).getBody());
  }
  @ApiOperation("验证token")
  @GetMapping("/check_token")
  public R<Object> checkToken(@RequestParam("token") String value) {
    return R.data(checkTokenEndpoint.checkToken(value));
  }
}

好了,大功告成。。

具体测试那些,这个版本是写了很久了的,所以测试那些截图都没有了。

代码由于笔者之前太忙了,忘了测试和保存了。所以会存在一些问题,而且这些代码网上也很多,这里不详细解释了。后续更新自定义passwordToken和referToken。