zoukankan      html  css  js  c++  java
  • 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.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方法的源码,一看就不言而喻了。

      

    六、源码下载

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

  • 相关阅读:
    Log4net 配置详解
    JS 浮点计算BUG
    EF 一对一,一对多,多对多 Flunt API 配置
    分享一个近期写的简单版的网页采集器
    Quartz.net Cron表达式
    客户端负载均衡—Ribbon初探
    服务注册与发现—Eureka初探
    第五坑:这颗语法糖不太甜(自动装箱拆箱)
    Redis学习笔记
    Java集合——HashMap
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/7171211.html
Copyright © 2011-2022 走看看