shiro 和 spring-security在功能上都是很好的框架,但Shiro 最大的问题在于和 Spring 家族的产品进行整合的时候非常不便,对比之下,虽然spring-security相对Shiro会复杂很多,但基于 Spring Boot/Spring Cloud 的微服务项目基本上是原生支持spring-security的,对接起来非常方便。因此选择spring-security。
//校验该方法中的response_type参数 Set<String> responseTypes = authorizationRequest.getResponseTypes(); if (!responseTypes.contains("token") && !responseTypes.contains("code")) { throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes); }
//校验是否提供clientId if (authorizationRequest.getClientId() == null) { throw new InvalidClientException("A client id must be provided"); }
try {
/** 这里非常重要!!!假如这里你没有登录,这里会直接抛出AuthenticationException, 而这个异常会被过滤器链filterChain上的spring security 专用的异常处理过滤器捕捉, 对应的是org.springframework.security.web.access.ExceptionTranslationFilter的105行 的handleSpringSecurityException这个方法,捕捉后根据不同的实现类 spring会执行不同的操作,对应于认证码模式,它就会重定向走 因此假如你的项目有全局异常处理器,这里一定要记得不要去捕捉所有异常,如果 把spring security的异常捕捉了,就会产生意料外的缺陷 **/ if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) { throw new InsufficientAuthenticationException( "User must be authenticated with Spring Security before authorization can be completed."); }
//解析并判断redirect_uri中指定的参数是否与后端配置的一样 String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI); String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client); if (!StringUtils.hasText(resolvedRedirect)) { throw new RedirectMismatchException( "A redirectUri must be either supplied or preconfigured in the ClientDetails"); } authorizationRequest.setRedirectUri(resolvedRedirect);
//假如你点了批准或者设置为默认批准 if (authorizationRequest.isApproved()) { //如果你的response_type是token,则这里直接返回token给前端 if (responseTypes.contains("token")) { return getImplicitGrantResponse(authorizationRequest); } //response_type为code,这里生成code后,return一个重定向的modelAndView完成跳转 if (responseTypes.contains("code")) { return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal)); } }
// Store authorizationRequest AND an immutable Map of authorizationRequest in session // which will be used to validate against in approveOrDeny() model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));
if (clientId != null && !clientId.equals("")) { // Only validate the client details if a client authenticated during this // request. if (!clientId.equals(tokenRequest.getClientId())) { //如果不相等或者没找到,则直接抛出异常 throw new InvalidClientException("Given client ID does not match authenticated client"); } } //校验scope是否匹配 if (authenticatedClient != null) { oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); } //下面都是校验各种参数 if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } if (tokenRequest.getGrantType().equals("implicit")) { throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); }
if (isAuthCodeRequest(parameters)) { // The scope was requested or determined during the authorization step if (!tokenRequest.getScope().isEmpty()) { logger.debug("Clearing scope of incoming token request"); tokenRequest.setScope(Collections.<String> emptySet()); } }
if (isRefreshTokenRequest(parameters)) { // A refresh token has its own default scopes, so we should ignore any added by the factory here. tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); }
//到这里就来到了整个token方法的核心,这一步会生成token,这里spring security设计的很好 //灵活性非常高,通过grantType为每个模式指定不同的TokenGranter,将生成token的操作 //委托给子类 OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); }