shiro整合oauth

前言

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

一、基本思路脑图

shiro整合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授权方法的逻辑

四、流程图

  shiro整合oauth

五、优化

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

  shiro整合oauth

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

  shiro整合oauth

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

  shiro整合oauth

六、源码下载

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

上一篇:BZOJ4381 : [POI2015]Odwiedziny / Luogu3591[POI2015]ODW - 分块+树剖


下一篇:[POI2015]Odwiedziny