shiro整合oauth

来源:互联网 发布:林俊杰家世 知乎 编辑:程序博客网 时间:2024/05/29 17:21

前言

  如果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.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的配置

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

  自定义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.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的配置

  自定义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 {    @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();    }}

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

  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 {    @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授权方法的逻辑

四、流程图

  

五、优化

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

  

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

  

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

  

六、源码下载

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

原创粉丝点击