当前位置: 首页 > 编程日记 > 正文

shiro整合oauth

前言

  如果oauth原理还不清楚的地方,其参考这里。 

一、基本思路脑图

二、客户端shiro配置

shiro配置文件

<?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.xsdhttp://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的配置

自定义Realm实现

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 infoOAuthClientRequest 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中如何获取 用户信息的

自定义Filter实现

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;  }  
}   

注重看一下 如何构造的 AuthToken

三、服务端配置

shiro配置文件

<?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.xsdhttp://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的配置

自定义Filter实现

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函数的逻辑

自定义Realm实现

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 {@Resourceprivate UserApiImpl userApi;@Overrideprotected 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;}@Overrideprotected 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+saltgetName()  //realm name
        );return authenticationInfo;}@Overridepublic void clearCachedAuthorizationInfo(PrincipalCollection principals) {super.clearCachedAuthorizationInfo(principals);}@Overridepublic void clearCachedAuthenticationInfo(PrincipalCollection principals) {super.clearCachedAuthenticationInfo(principals);}@Overridepublic void clearCache(PrincipalCollection principals) {super.clearCache(principals);}public void clearAllCachedAuthorizationInfo() {getAuthorizationCache().clear();}public void clearAllCachedAuthenticationInfo() {getAuthenticationCache().clear();}public void clearAllCache() {clearAllCachedAuthenticationInfo();clearAllCachedAuthorizationInfo();}
}

注重看下认证信息和权限信息的获取

oauth相关接口的实现

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 {@Resourceprivate UserApiImpl userApi;@Value(value = "#{config['expiresIn']}")private String expiresIn;/*** 获取授权码-服务端** @param request* @return* @throws OAuthProblemException* @throws OAuthSystemException*/@RequestMapping(value = "/authorize", method = RequestMethod.GET)@ResponseBodypublic 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客户端idString 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和TOKENif (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)@ResponseBodypublic Object accessToken(HttpServletRequest request) throws OAuthProblemException, OAuthSystemException {try {// 构建OAuth请求OAuthTokenRequest tokenRequest = new OAuthTokenRequest(request);// 1.获取OAuth客户端idString 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_TOKENif (!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 TokenOAuthIssuer 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")@ResponseBodypublic 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)@ResponseBodypublic Object userInfo(HttpServletRequest request) throws OAuthSystemException, OAuthProblemException {try {//构建OAuth资源请求OAuthAccessResourceRequest oauthRequest =new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);//获取Access TokenString accessToken = oauthRequest.getAccessToken();//验证Access TokenResponseEntity 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授权方法的逻辑

四、流程图

五、优化

服务端获取登录前url可以利用shiro的一个工具类WebUtils

客户端向服务端发起授权请求时,如果服务端没有登录则先将对应的URL存储起来并重定向到服务端的登录页,待服务端登录成功之后,FormAuthenticationFilter会调用onLoginSuccess方法。

再看看issueSuccessRedirect方法的源码,一看就不言而喻了。

六、源码下载

  更多详细信息,请参考源码哦!

相关文章:

2010年的退休畅想

有了确定的目标应该是终究可以实现的&#xff0c;比如新买的房子&#xff0c;每一次路过那个地方都要心里暗暗向往&#xff0c;闹市中还能这么安静的地方&#xff0c;托人也买不到的地方&#xff0c;以为注定与我无缘&#xff0c;金融危机让大家都平等了&#xff0c;于是拥有了…

【怎样写代码】偷窥高手 -- 反射技术(三):深入窥视字段

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

android studio 库项目管理,在Android Studio中将现有项目转换为库项目

在模块的applicationId文件中(如果使用模块&#xff0c;则不是根项目&#xff01;)&#xff0c;只需替换&#xff1a;apply plugin: com.android.application// or, if youre on an old versionapply plugin: android // note: this one is deprecated...具有&#xff1a;apply…

WSDL、SOAP、UDDI

纵观计算机和软件领域&#xff0c;我们不难了解为什么会产生Web服务。在因特网上有许多系统和平台&#xff0c;在这些系统和平台上又有更多的应用程序。说得更明白些就是&#xff0c;存在着许多技术&#xff0c;把客户端连接到服务器&#xff0c;这其中包括DCOM、CORBA和其它各…

实战证明LINUX系统下密钥对验证的安全性

实战证明LINUX系统下密钥对验证的安全性 密钥对验证&#xff1a;要求提供匹配的密钥信息才能通过验证&#xff0c;首先在客户端创建一对密钥文件&#xff08;公钥、私钥&#xff09;&#xff0c;后把公钥文件放到需要远程连接的服务器中。远程登录时&#xff0c;系统将使用私钥…

【怎样写代码】偷窥高手 -- 反射技术(四):深入窥视属性

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

百度android 测试平台,集成百度活体检测(Android、iOS)

更新记录1.1.0(2020-06-18)插件增加iOS版本1.0.2(2020-06-08)优化插件打包无法使用的问题插件调用方法添加sound字段&#xff0c;表示是否开启声音&#xff0c;true为开启&#xff0c;默认false查看更多平台兼容性AndroidiOS适用版本区间&#xff1a;4.4 - 9.0 支持CPU类型&…

ubuntukylin-14.04.2-desktop-amd64中python2.7版本安装机器学习库

为什么80%的码农都做不了架构师&#xff1f;>>> 本文永久地址&#xff1a;https://my.oschina.net/bysu/blog/1456737 1.如果需要设置代理才能上网&#xff0c;那么先设置代理。 摘自&#xff1a;http://www.cnblogs.com/foonsun/p/5781767.html ubuntu 全局代理&a…

【怎样写代码】偷窥高手 -- 反射技术(五):深入窥视方法

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

android 系统锁屏音乐播放器,Android实现音乐播放器锁屏页

本文实例为大家分享了Android音乐播放器锁屏页的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下首页我们先看一下效果图下边来说一下实现逻辑&#xff0c;其主要思路就是新建一个activity使其覆盖在锁屏页上边。一、我们新建一个LockActivty&#xff0c;既然是四大组…

GridView标题行换行之我见 (转)

GridView标题行换行之我见 (转) 1、不换行&#xff1a;word-break:keep-all;word-wrap:normal2、换行&#xff1a;word-break:break-all;word-wrap:break-word以上的换行只支持英文&#xff0c;当标题中包含有中文时并不好用。3、如果标题中含有中文的&#xff0c;只能在GridVi…

【怎样写代码】偷窥高手 -- 反射技术(六):深入窥视DLL内部

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

GIS+=地理信息+云计算技术——Spark集群部署

第一步&#xff1a;安装软件 Spark 1.5.4:wget http://www.apache.org/dyn/closer.lua/spark/spark-1.5.2/spark-1.5.2-bin-hadoop2.6.tgzHadoop 2.6.3:wget http://www.apache.org/dyn/closer.cgi/hadoop/common/hadoop-2.6.3/hadoop-2.6.3.tar.gzscala :apt-get inst…

android上传图片崩溃,导致安卓手机死机的照片拍摄者表示这张照片是无意之举...

原标题&#xff1a;导致安卓手机死机的照片拍摄者表示这张照片是无意之举上周&#xff0c;我们报道了在一些安卓手机上将某张图片设置为壁纸会导致手机崩溃&#xff0c;并卡在一个开启和关闭显示屏的循环中&#xff0c;让用户无法进行锁屏。它影响了大多数Android手机&#xff…

判断页面元素存在与否

在传统的Javascript里&#xff0c;当我们对某个页面元素进行某种操作前&#xff0c;最好先判断这个元素是否存在。原因是对一个不存在的元素进行操作是不允许的。例如&#xff1a;document.getElementById("someID").innerText("hi");如果ID为"someID…

Java系列 – 用Java8新特性进行Java开发太爽了(续)

本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome-2/前言上周, 我们谈论了关于Java8的新特性有那些, 什么是函数式编程, 什么是Lambda表达式, 这周让我们继续谈论这些新特性.本周, 我们会聊一下什么是Stream API, 以及什么是Optiona…

【怎样写代码】偷窥高手 -- 反射技术(七):通过反射实例化对象

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

腾讯全力支持鸿蒙,腾讯宣布大力发展车联网,或与华为鸿蒙强强联合!

原标题&#xff1a;腾讯宣布大力发展车联网&#xff0c;或与华为鸿蒙强强联合&#xff01;一场全球级别的科技风暴已然拉开序幕&#xff0c;互联网、通讯系统、云计算、智能家居、汽车……万物即将联为一体。按理来说这是互联网巨头大展拳脚的好机会&#xff0c;但身为国内互联…

CreateFileMapping

CreateFileMapping VB声明 Declare Function CreateFileMapping Lib "kernel32" Alias "CreateFileMappingA" (ByVal hFile As Long, lpFileMappigAttributes As SECURITY_ATTRIBUTES, ByVal flProtect As Long, ByVal dwMaximumSizeHigh As Long, ByVal…

java file 操作之创建、删除文件及文件夹

本文章向大家讲解java文件的基本操作&#xff0c;包括java创建文件和文件夹、java删除文件、java获取指定目录的全部文件、java判断指定路径是否为目录以及java搜索指定目录的全部内容等。请看下面实例。 创建文件File 的两个常量&#xff08;File.separator、File.pathSeparat…

【怎样写代码】小技巧 -- .NET配置文件详解

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

html树状结构怎么展开,纯css实现树形结构

纯css实现属性结构**css实现属性结构的思路是利用伪类实现树形结构连接线&#xff0c;如果想实现点击展开和收缩以及复选框效果还得配合js来实现。其实展开和收缩就是一个点击元素其子元素隐藏和显示的切换。**效果图html结构1级菜单2级菜单2级菜单3级菜单3级菜单1级菜单2级菜单…

.net获取ip地址

/// 获得客户端IP /// /// <returns></returns> private string getIp() { // 穿过代理服务器取远程用户真实IP地址 string Ip string.Empty; if (Request.ServerVariables["HTTP_VIA"] ! null) { …

Pascal 错误代码及含义

DOS 错误代码&#xff1a;1无效DoS功能号 2文件末找到 3路径未找到 4打开文件过多 5禁止文件存取 6无效文件句柄 12无效文件存取代码 15无效驱动器号 16不能删除当前日录 17不能跨驱动器改文件名 I/O错误 100磁盘读错误 101磁盘写错误 102文件变量未赋值 103文件未打开 104文件…

【怎样写代码】工厂三兄弟之工厂方法模式(一):问题案例

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

html5展示json数据库,显示数据在html5从数据库使用javascript和json

我想从MySQL数据库显示在html中的列表。要从MySQL获取数据我使用json和javascript。但它什么也没有显示&#xff01;显示数据在html5从数据库使用javascript和json我的HTML页面BackMy Lease当我从浏览器ID叫我的PHP文件is.It工作正常。session_start();include connection.php;…

Python学习笔记二:布尔表达式

1 #coding:utf-82 #布尔表达式的值只有两个&#xff0c;True和False3 x 12.44 y 12.35 printxy #符号用于判断两个数是否相等&#xff0c;这条语句的resultFalse6 x12.37 printx y #这条语句的result True8 printx !y #符号!用于判断两个数是否不相等&#xff0c;这条语句的re…

【怎样写代码】工厂三兄弟之工厂方法模式(二):解决方案 I

如果喜欢这里的内容&#xff0c;你能够给我最大的帮助就是转发&#xff0c;告诉你的朋友&#xff0c;鼓励他们一起来学习。 If you like the content here, you can give me the greatest help is forwarding, tell your friends, encourage them to learn together.

html中怎么隐藏复选框,隐藏复选框字段HTML

替代他们&#xff0c;我改变了你的标记&#xff0c;使用形式。 JavaScript是没有必要的(除非你需要支持IE8以上的浏览器)每一个复选框包括它的标签&#xff0c;它是透明的&#xff1a;所以当你点击你实际点击复选框的颜色。所选颜色的不同样式通过:checked伪类应用。标记GrayRe…

新公司研发能力低下,何去何从?

我是去年年底辞职的&#xff0c;到了现在的新公司&#xff0c;和我一起进公司的还有个09年毕业的研究生。到了公司不多久&#xff0c;我们就参加到了一个项目研发中&#xff0c;在研发的过程中&#xff0c;我们都发现公司的代码简直就是垃圾&#xff1a;一&#xff1a;代码的命…