若是oauth原理還不清楚的地方,其參考這裏。 html
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 緩存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache/ehcache.xml"/> </bean> <!-- Realm實現 --> <bean id="oAuth2Realm" class="com.hjzgg.auth.client.shiro.OAuth2Realm"> <property name="cachingEnabled" value="true"/> <property name="authenticationCachingEnabled" value="true"/> <property name="authenticationCacheName" value="authenticationCache"/> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="authorizationCache"/> <property name="clientId" value="c1ebe466-1cdc-4bd3-ab69-77c3561b9dee"/> <property name="clientSecret" value="d8346ea2-6017-43ed-ad68-19c0f971738b"/> <property name="accessTokenUrl" value="http://127.0.0.1:8080/auth-web/oauth/accessToken"/> <property name="userInfoUrl" value="http://127.0.0.1:8080/auth-web/oauth/userInfo"/> <property name="redirectUrl" value="http://127.0.0.1:8080/auth-client/login"/> </bean> <!-- 會話ID生成器 --> <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/> <!-- 會話Cookie模板 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="-1"/> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="2592000"/><!-- 30天 --> </bean> <!-- rememberMe管理器 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <!-- rememberMe cookie加密的密鑰 建議每一個項目都不同 默認AES算法 密鑰長度(128 256 512 位)--> <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/> <property name="cookie" ref="rememberMeCookie"/> </bean> <!-- 會話DAO --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/> <property name="sessionIdGenerator" ref="sessionIdGenerator"/> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="1800000"/> <property name="deleteInvalidSessions" value="true"/> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionDAO" ref="sessionDAO"/> <property name="sessionIdCookieEnabled" value="true"/> <property name="sessionIdCookie" ref="sessionIdCookie"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="oAuth2Realm"/> <property name="sessionManager" ref="sessionManager"/> <property name="cacheManager" ref="cacheManager"/> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- 至關於調用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <!-- OAuth2身份驗證過濾器 --> <bean id="oAuth2AuthenticationFilter" class="com.hjzgg.auth.client.shiro.OAuth2AuthenticationFilter"> <property name="authcCodeParam" value="code"/> <property name="failureUrl" value="/oauth2Failure.jsp"/> </bean> <!-- Shiro的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="http://127.0.0.1:8080/auth-web/oauth/authorize?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://127.0.0.1:8080/oauth-client/login"/> <property name="successUrl" value="/index.jsp"/> <property name="filters"> <util:map> <entry key="oauth2Authc" value-ref="oAuth2AuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /oauth2Failure.jsp = anon /login = oauth2Authc /logout = logout /** = user </value> </property> </bean> <!-- Shiro生命週期處理器--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> </beans>
注重看一下Realm的參數配置和 shiroFilter loginUrl的配置java
package com.hjzgg.auth.client.shiro; import org.apache.oltu.oauth2.client.OAuthClient; import org.apache.oltu.oauth2.client.URLConnectionClient; import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest; import org.apache.oltu.oauth2.client.request.OAuthClientRequest; import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse; import org.apache.oltu.oauth2.client.response.OAuthResourceResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.message.types.GrantType; 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; public class OAuth2Realm extends AuthorizingRealm { private String clientId; private String clientSecret; private String accessTokenUrl; private String userInfoUrl; private String redirectUrl; public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getClientSecret() { return clientSecret; } public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } public String getAccessTokenUrl() { return accessTokenUrl; } public void setAccessTokenUrl(String accessTokenUrl) { this.accessTokenUrl = accessTokenUrl; } public String getUserInfoUrl() { return userInfoUrl; } public void setUserInfoUrl(String userInfoUrl) { this.userInfoUrl = userInfoUrl; } public String getRedirectUrl() { return redirectUrl; } public void setRedirectUrl(String redirectUrl) { this.redirectUrl = redirectUrl; } public boolean supports(AuthenticationToken token) { return token instanceof OAuth2Token; //表示此Realm只支持OAuth2Token類型 } protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); return authorizationInfo; } protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { OAuth2Token oAuth2Token = (OAuth2Token) token; String code = oAuth2Token.getAuthCode(); //獲取 auth code String username = extractUsername(code); // 提取用戶名 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, code, getName()); return authenticationInfo; } private String extractUsername(String code) { try { OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(accessTokenUrl) .setGrantType(GrantType.AUTHORIZATION_CODE) .setClientId(clientId).setClientSecret(clientSecret) .setCode(code).setRedirectURI(redirectUrl) .buildQueryMessage(); //獲取access token OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST); String accessToken = oAuthResponse.getAccessToken(); //獲取user info OAuthClientRequest userInfoRequest = new OAuthBearerClientRequest(userInfoUrl) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse = oAuthClient.resource( userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); String username = resourceResponse.getBody(); return username; } catch (Exception e) { throw new OAuth2AuthenticationException(e); } } }
注重看一下realm中如何獲取 用戶信息的git
package com.hjzgg.auth.client.shiro; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.web.util.WebUtils; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class OAuth2AuthenticationFilter extends AuthenticatingFilter { //oauth2 authc code參數名 private String authcCodeParam = "code"; //客戶端id private String clientId; //服務器端登陸成功/失敗後重定向到的客戶端地址 private String redirectUrl; //oauth2服務器響應類型 private String responseType = "code"; private String failureUrl; public String getAuthcCodeParam() { return authcCodeParam; } public void setAuthcCodeParam(String authcCodeParam) { this.authcCodeParam = authcCodeParam; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getRedirectUrl() { return redirectUrl; } public void setRedirectUrl(String redirectUrl) { this.redirectUrl = redirectUrl; } public String getResponseType() { return responseType; } public void setResponseType(String responseType) { this.responseType = responseType; } public String getFailureUrl() { return failureUrl; } public void setFailureUrl(String failureUrl) { this.failureUrl = failureUrl; } protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpRequest = (HttpServletRequest) request; String code = httpRequest.getParameter(authcCodeParam); return new OAuth2Token(code); } protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { String error = request.getParameter("error"); String errorDescription = request.getParameter("error_description"); if(!StringUtils.isEmpty(error)) {//若是服務端返回了錯誤 WebUtils.issueRedirect(request, response, failureUrl + "?error=" + error + "error_description=" + errorDescription); return false; } Subject subject = getSubject(request, response); if(!subject.isAuthenticated()) { if(StringUtils.isEmpty(request.getParameter(authcCodeParam))) { //若是用戶沒有身份驗證,且沒有auth code,則重定向到服務端受權 saveRequestAndRedirectToLogin(request, response); return false; } } //執行父類裏的登陸邏輯,調用Subject.login登陸 return executeLogin(request, response); } //登陸成功後的回調方法 重定向到成功頁面 protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { issueSuccessRedirect(request, response); return false; } //登陸失敗後的回調 protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request, ServletResponse response) { Subject subject = getSubject(request, response); if (subject.isAuthenticated() || subject.isRemembered()) { try { //若是身份驗證成功了 則也重定向到成功頁面 issueSuccessRedirect(request, response); } catch (Exception e) { e.printStackTrace(); } } else { try { //登陸失敗時重定向到失敗頁面 WebUtils.issueRedirect(request, response, failureUrl); } catch (IOException e) { e.printStackTrace(); } } return false; } }
注重看一下 如何構造的 AuthTokengithub
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 緩存管理器 --> <bean id="cacheManager" class="com.hjzgg.auth.util.SpringCacheManagerWrapper"> <property name="cacheManager" ref="springCacheManager"/> </bean> <bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcacheManager"/> </bean> <!--ehcache--> <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache/ehcache.xml"/> </bean> <!-- 憑證匹配器 --> <bean id="credentialsMatcher" class="com.hjzgg.auth.shiro.RetryLimitHashedCredentialsMatcher"> <constructor-arg ref="cacheManager"/> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="2"/> <property name="storedCredentialsHexEncoded" value="true"/> </bean> <!-- Realm實現 --> <bean id="userRealm" class="com.hjzgg.auth.shiro.UserRealm"> <!--<property name="credentialsMatcher" ref="credentialsMatcher"/>--> <property name="cachingEnabled" value="false"/> <!--<property name="authenticationCachingEnabled" value="true"/>--> <!--<property name="authenticationCacheName" value="authenticationCache"/>--> <!--<property name="authorizationCachingEnabled" value="true"/>--> <!--<property name="authorizationCacheName" value="authorizationCache"/>--> </bean> <!-- 會話ID生成器 --> <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/> <!-- 會話Cookie模板 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="-1"/> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="2592000"/><!-- 30天 --> </bean> <!-- rememberMe管理器 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <!-- rememberMe cookie加密的密鑰 建議每一個項目都不同 默認AES算法 密鑰長度(128 256 512 位)--> <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/> <property name="cookie" ref="rememberMeCookie"/> </bean> <!-- 會話DAO --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/> <property name="sessionIdGenerator" ref="sessionIdGenerator"/> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="1800000"/> <property name="deleteInvalidSessions" value="true"/> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionDAO" ref="sessionDAO"/> <property name="sessionIdCookieEnabled" value="true"/> <property name="sessionIdCookie" ref="sessionIdCookie"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"/> <property name="sessionManager" ref="sessionManager"/> <property name="cacheManager" ref="cacheManager"/> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- 至關於調用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <bean name="formAuthenticationFilter" class="com.hjzgg.auth.shiro.FormAuthenticationFilter"/> <!-- Shiro的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> </util:map> </property> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/index.jsp"/> <property name="filterChainDefinitions"> <value> /logout = logout /login.jsp = authc /oauth/authorize=anon /oauth/accessToken=anon /oauth/userInfo=anon /** = roles[admin] </value> </property> </bean> <!-- Shiro生命週期處理器--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> </beans>
注重看一下filterChainDefinitions的配置web
package com.hjzgg.auth.shiro; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.oltu.oauth2.common.OAuth; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FormAuthenticationFilter extends AuthenticatingFilter { public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure"; public static final String DEFAULT_USERNAME_PARAM = "username"; public static final String DEFAULT_PASSWORD_PARAM = "password"; public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe"; private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilter.class); private String usernameParam = "username"; private String passwordParam = "password"; private String rememberMeParam = "rememberMe"; private String failureKeyAttribute = "shiroLoginFailure"; public FormAuthenticationFilter() { this.setLoginUrl("/login.jsp"); } public void setLoginUrl(String loginUrl) { String previous = this.getLoginUrl(); if(previous != null) { this.appliedPaths.remove(previous); } super.setLoginUrl(loginUrl); if(log.isTraceEnabled()) { log.trace("Adding login url to applied paths."); } this.appliedPaths.put(this.getLoginUrl(), (Object)null); } public String getUsernameParam() { return this.usernameParam; } public void setUsernameParam(String usernameParam) { this.usernameParam = usernameParam; } public String getPasswordParam() { return this.passwordParam; } public void setPasswordParam(String passwordParam) { this.passwordParam = passwordParam; } public String getRememberMeParam() { return this.rememberMeParam; } public void setRememberMeParam(String rememberMeParam) { this.rememberMeParam = rememberMeParam; } public String getFailureKeyAttribute() { return this.failureKeyAttribute; } public void setFailureKeyAttribute(String failureKeyAttribute) { this.failureKeyAttribute = failureKeyAttribute; } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if(this.isLoginRequest(request, response)) { if(this.isLoginSubmission(request, response)) { if(log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return this.executeLogin(request, response); } else { if(log.isTraceEnabled()) { log.trace("Login page view."); } return true; } } else { if(log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [" + this.getLoginUrl() + "]"); } this.saveRequestAndRedirectToLogin(request, response); return false; } } protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) { return request instanceof HttpServletRequest && WebUtils.toHttp(request).getMethod().equalsIgnoreCase("POST"); } protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = this.getUsername(request); String password = this.getPassword(request); return this.createToken(username, password, request, response); } protected boolean isRememberMe(ServletRequest request) { return WebUtils.isTrue(request, this.getRememberMeParam()); } protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { if(StringUtils.isNotEmpty(this.getResponseType(request)) && StringUtils.isNotEmpty(this.getRedirectURI(request))) { String authorizeURI = "/oauth/authorize?"; this.setSuccessUrl(authorizeURI + ((HttpServletRequest)request).getQueryString()); } this.issueSuccessRedirect(request, response); return false; } protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { if(log.isDebugEnabled()) { log.debug("Authentication exception", e); } this.setFailureAttribute(request, e); return true; } protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) { String className = ae.getClass().getName(); request.setAttribute(this.getFailureKeyAttribute(), className); } protected String getUsername(ServletRequest request) { return WebUtils.getCleanParam(request, this.getUsernameParam()); } protected String getPassword(ServletRequest request) { return WebUtils.getCleanParam(request, this.getPasswordParam()); } private String getRedirectURI(ServletRequest request) { return WebUtils.getCleanParam(request, OAuth.OAUTH_REDIRECT_URI); } private String getResponseType(ServletRequest request) { return WebUtils.getCleanParam(request, OAuth.OAUTH_RESPONSE_TYPE); } }
注重看一下onLoginSuccess函數的邏輯算法
package com.hjzgg.auth.shiro; import com.hjzgg.auth.domain.dto.LightUserResult; import com.hjzgg.auth.service.UserApiImpl; 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 javax.annotation.Resource; import java.util.Arrays; import java.util.HashSet; /** * <p>Version: 1.0 */ public class UserRealm extends AuthorizingRealm { @Resource private UserApiImpl userApi; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String)principals.getPrimaryPrincipal(); LightUserResult user = userApi.queryUserByName(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(new HashSet<>(Arrays.asList(user.getRole()))); //暫時不加權限 return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String)token.getPrincipal(); LightUserResult user = userApi.queryUserByName(username); if(user == null) { throw new UnknownAccountException();//沒找到賬號 } //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,若是以爲人家的很差能夠自定義實現 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user.getUserName(), //用戶名 user.getPassword(), //密碼 //ByteSource.Util.bytes(user.getPassword()),//salt=username+salt getName() //realm name ); return authenticationInfo; } @Override public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); } @Override public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals); } @Override public void clearCache(PrincipalCollection principals) { super.clearCache(principals); } public void clearAllCachedAuthorizationInfo() { getAuthorizationCache().clear(); } public void clearAllCachedAuthenticationInfo() { getAuthenticationCache().clear(); } public void clearAllCache() { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); } }
注重看下認證信息和權限信息的獲取spring
package com.hjzgg.auth.controller; import com.alibaba.fastjson.JSONObject; import com.hjzgg.auth.domain.dto.LightUserResult; import com.hjzgg.auth.service.UserApiImpl; import com.hjzgg.auth.util.OAuthValidate; import com.hjzgg.auth.util.RedisUtil; import org.apache.commons.lang3.StringUtils; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; import org.apache.oltu.oauth2.as.request.OAuthTokenRequest; import org.apache.oltu.oauth2.as.response.OAuthASResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.common.message.types.ParameterStyle; import org.apache.oltu.oauth2.common.message.types.ResponseType; import org.apache.oltu.oauth2.common.utils.OAuthUtils; import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest; import org.apache.oltu.oauth2.rs.response.OAuthRSResponse; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.URI; import java.net.URISyntaxException; /** * Created by hujunzheng on 2017/5/23. */ @Controller @RequestMapping(value = "oauth") public class OAuthController { @Resource private UserApiImpl userApi; @Value(value = "#{config['expiresIn']}") private String expiresIn; /** * 獲取受權碼-服務端 * * @param request * @return * @throws OAuthProblemException * @throws OAuthSystemException */ @RequestMapping(value = "/authorize", method = RequestMethod.GET) @ResponseBody public Object authorize(HttpServletRequest request) throws URISyntaxException, OAuthProblemException, OAuthSystemException { try { // 構建OAuth受權請求 OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); //獲得到客戶端重定向地址 String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); // 1.獲取OAuth客戶端id String clientId = oauthRequest.getClientId(); // 校驗客戶端id是否正確 LightUserResult lightUserResult = userApi.queryUserByClientId(clientId); if (null == lightUserResult) { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription("無效的客戶端ID") .buildJSONMessage(); return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } Subject subject = SecurityUtils.getSubject(); //若是用戶沒有登陸,跳轉到登錄頁面 if (!subject.isAuthenticated()) { if (!login(subject, request)) {//登陸失敗時跳轉到登錄頁面 HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(request.getContextPath() + "/login.jsp?" + OAuth.OAUTH_REDIRECT_URI + "=" + redirectURI + "&" + OAuth.OAUTH_RESPONSE_TYPE + "=" + responseType + "&" + OAuth.OAUTH_CLIENT_ID + "=" + clientId) ); return new ResponseEntity(headers, HttpStatus.TEMPORARY_REDIRECT); } } // 2.生成受權碼 String authCode = null; // ResponseType僅支持CODE和TOKEN if (responseType.equals(ResponseType.CODE.toString())) { OAuthIssuerImpl oAuthIssuer = new OAuthIssuerImpl(new MD5Generator()); authCode = oAuthIssuer.authorizationCode(); // 存入緩存中authCode-username RedisUtil.getRedis().set(authCode, lightUserResult.getUserName()); } //進行OAuth響應構建 OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND); //設置受權碼 builder.setCode(authCode); //構建響應 final OAuthResponse response = builder.location(redirectURI).buildQueryMessage(); //根據OAuthResponse返回ResponseEntity響應 HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(response.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthProblemException e) { //出錯處理 String redirectUri = e.getRedirectUri(); if (OAuthUtils.isEmpty(redirectUri)) { //告訴客戶端沒有傳入redirectUri直接報錯 return new ResponseEntity( "OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND); } //返回錯誤消息(如?error=) final OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND) .error(e).location(redirectUri).buildQueryMessage(); HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(response.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); } catch (Exception e) { return new ResponseEntity("內部錯誤", HttpStatus.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); } } private boolean login(Subject subject, HttpServletRequest request) { if ("get".equalsIgnoreCase(request.getMethod())) { return false; } String username = request.getParameter("username"); String password = request.getParameter("password"); if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { return false; } UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return true; } catch (Exception e) { request.setAttribute("error", "登陸失敗:" + e.getClass().getName()); return false; } } /** * 獲取訪問令牌 * * @param request * @return * @throws OAuthProblemException * @throws OAuthSystemException */ @RequestMapping(value = "accessToken", method = RequestMethod.POST) @ResponseBody public Object accessToken(HttpServletRequest request) throws OAuthProblemException, OAuthSystemException { try { // 構建OAuth請求 OAuthTokenRequest tokenRequest = new OAuthTokenRequest(request); // 1.獲取OAuth客戶端id String clientId = tokenRequest.getClientId(); // 校驗客戶端id是否正確 LightUserResult lightUserResult = userApi.queryUserByClientId(clientId); if (null == lightUserResult) { OAuthResponse oAuthResponse = OAuthResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription("無效的客戶端ID") .buildJSONMessage(); return new ResponseEntity(oAuthResponse.getBody(), HttpStatus.valueOf(oAuthResponse.getResponseStatus())); } // 2.檢查客戶端安全key是否正確 if (!lightUserResult.getClientSecret().equals(tokenRequest.getClientSecret())) { OAuthResponse oAuthResponse = OAuthResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT) .setErrorDescription("客戶端安全key認證不經過") .buildJSONMessage(); return new ResponseEntity<>(oAuthResponse.getBody(), HttpStatus.valueOf(oAuthResponse.getResponseStatus())); } // 3.檢查受權碼是否正確 String authCode = tokenRequest.getParam(OAuth.OAUTH_CODE); // 檢查驗證類型,此處只檢查AUTHORIZATION_CODE類型,其餘的還有password或REFRESH_TOKEN if (!tokenRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())) { if (null == RedisUtil.getRedis().get(authCode)) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_GRANT) .setErrorDescription("受權碼錯誤") .buildJSONMessage(); return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } } // 4.生成訪問令牌Access Token OAuthIssuer oAuthIssuer = new OAuthIssuerImpl(new MD5Generator()); final String accessToken = oAuthIssuer.accessToken(); // 將訪問令牌加入緩存:accessToken-username RedisUtil.getRedis().set(accessToken, lightUserResult.getUserName()); // 5.生成OAuth響應 OAuthResponse response = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken) .setExpiresIn(expiresIn) .buildJSONMessage(); return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity("內部錯誤", HttpStatus.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); } } @RequestMapping("validate") @ResponseBody public JSONObject oauthValidate(HttpServletRequest request) throws OAuthSystemException, OAuthProblemException { ResponseEntity responseEntity = OAuthValidate.oauthValidate(request); JSONObject result = new JSONObject(); result.put("msg", responseEntity.getBody()); result.put("code", responseEntity.getStatusCode().value()); return result; } @RequestMapping(value = "userInfo", method = RequestMethod.GET) @ResponseBody public Object userInfo(HttpServletRequest request) throws OAuthSystemException, OAuthProblemException { try { //構建OAuth資源請求 OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.QUERY); //獲取Access Token String accessToken = oauthRequest.getAccessToken(); //驗證Access Token ResponseEntity responseEntity = OAuthValidate.oauthValidate(request); if (responseEntity.getStatusCode() != HttpStatus.OK) { // 若是不存在/過時了,返回未驗證錯誤,需從新驗證 OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setRealm("auth-web") .setError(OAuthError.ResourceResponse.INVALID_TOKEN) .buildHeaderMessage(); HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED); } //返回用戶名 String username = RedisUtil.getRedis().get(accessToken); return new ResponseEntity(username, HttpStatus.OK); } catch (OAuthProblemException e) { //檢查是否設置了錯誤碼 String errorCode = e.getError(); if (OAuthUtils.isEmpty(errorCode)) { OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setRealm("auth-web") .buildHeaderMessage(); HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED); } OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setRealm("auth-web") .setError(e.getError()) .setErrorDescription(e.getDescription()) .setErrorUri(e.getUri()) .buildHeaderMessage(); HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(HttpStatus.BAD_REQUEST); } } }
注重看一下authorize受權方法的邏輯apache
服務端獲取登陸前url能夠利用shiro的一個工具類WebUtilsjson
客戶端向服務端發起受權請求時,若是服務端沒有登陸則先將對應的URL存儲起來並重定向到服務端的登陸頁,待服務端登陸成功以後,FormAuthenticationFilter會調用onLoginSuccess方法。緩存
再看看issueSuccessRedirect方法的源碼,一看就不言而喻了。
更多詳細信息,請參考源碼哦!