本人自己 研究及综合部分网上资料,整理得出,欢迎批评指正,欢迎一起探讨:
1.jar版本:
jdk1.6、spring3.1.1、spring-security3.1.0、spring-security-oauth2-1.0.5
2.背景:
客户公司 已经存在的N个系统,且每个系统用户名会有不一致情况,用户使用系统时需要多个系统协同操作,弊端凸显。为此,需要一个统一认证中心。
3.前提:
3.1:以上N个系统的用户是统一一个出处,如:一个AD域、数据库、LDAP等
3.2:app接入OAuth2的AuthorizationCode方式(其他方式本人未调试过)
3.3:OAuth的5个表已经生成到中心数据库中,并与中心app注册功能整合在一起。
4.定义:
4.1AbstractDomain.java:其中guid默认赋值为uuid,该类覆盖equals、hashCode,guid用户标识用户对象
的创建的唯一性,即同一用户在不同客户端登陆后,用户对象不同。
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AbstractDomain)) {
return false;
}
AbstractDomain that = (AbstractDomain) o;
return guid.equals(that.guid);
}
@Override
public int hashCode() {
return guid.hashCode();
}
4.2用户对象User.java:继承AbstractDomain,是中心的用户对象,也是OAuth接口获取用户信息的bean。
5.思路:
使用cookie缓存用户登录信息,app向中心登录时,获取该cookie,同时验证中心的session中是否存在该用户,存在则执行单点登录。
认证中心为一个CAS服务,同时需要app向中心注册,并提供appCode及redirectUrl,便于OAuth认证时调用。客户端登陆成功后,中心会向客户端浏览器写入cookie,保存登陆用户信息,作为单点登录凭据,用户关闭浏览器时,cookie自动失效。
6.典型场景:
6.1:用户在浏览器上输入app地址,app过滤器验证未登录后,附上appCode,然后发起OAuth第一步认证请求,中心过滤器判断用户是否登录,
6.1.1未登录:则会跳转到登陆页面,用户填写用户名密码进行登录,中心获取到登录成功后(向客户端写
入cookie),会继续执行OAuth认证,此时会弹出用户授权页面,当用户点击授权按钮后,转6.1.3
6.1.2已登录:转到授权页面(如果之前已经授权,会继续往下走),转6.1.3
6.1.3中心获取到授权后生成授权码,然后跳转到app的redirectUrl,app获取到授权码,根据授权码继续发送认证第二步请求,以授权码换取token,获取到token后,根据token访问中心提供的用户接口获取用户信息(json),app使用用户信息构建app的session、权限等数据,之后做具体业务操作,认证完成。
6.2:用户在该浏览器地址栏再输入app2的地址时,app2也发起OAuth第一步认证,中心仍然跳转到登陆页,此时登陆页获取cookie中用户名与guid信息,自动提交登录信息到action中验证session中是否存在该用户,不存在则调整到登录页面,存在则跳转到app2的redirectUrl,继续向下执行6.1.2操作,以完成认证。
7.数据流程图:
8.代码:纯干货
8.1web.xml:
认证中心过滤器,放过一些请求
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>*.util.LoginFilter</filter-class>
<init-param>
<param-name>AllowRes</param-name>
<param-value>
/login.do;/login.jsp;css;jpg;gif;png;bmp;jpeg;swf;ico;/demo/*;
/authTypeList.do;/authFail.do; /oauth/authorize;/oauth/token;/oauth/user_info.do;
isAgainAuth4Client.do
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器代理
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
额外添加spring session监听,用来处理在线用户
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
8.2security.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="oauth2AuthenticationManager"
entry-point-ref="oauth2AuthenticationEntryPoint">
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/>
<anonymous enabled="false"/>
<http-basic entry-point-ref="oauth2AuthenticationEntryPoint"/>
<custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER"/>
<access-denied-handler ref="oauth2AccessDeniedHandler"/>
</http>
<!--unity http configuration-->
<http pattern="/unity/**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint"
access-decision-manager-ref="oauth2AccessDecisionManager">
<anonymous enabled="false"/>
<intercept-url pattern="/unity/**" access="ROLE_UNITY,SCOPE_READ"/>
<custom-filter ref="unityResourceServer" before="PRE_AUTH_FILTER"/>
<access-denied-handler ref="oauth2AccessDeniedHandler"/>
</http>
<http auto-config="true" access-denied-page="/ac/login/authFail.do?flag=2" disable-url-rewriting="true" authentication-manager-ref="authenticationManager">
<intercept-url pattern="/oauth/**" access="ROLE_USER,ROLE_UNITY,ROLE_MOBILE"/>
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<form-login login-processing-url="/login.do" login-page="/ac/portal/login1.jsp" default-target-url="/portal.do"
authentication-failure-url="/ac/login/authFail.do"/>
<logout logout-success-url="/ac/portal/login1.jsp?flag=out" logout-url="/logout.do"/>
<anonymous />
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<session-management session-authentication-strategy-ref="sessionStrategy"/>
</http>
<beans:bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<beans:property name="authenticationManager" ref="oauth2AuthenticationManager"/>
</beans:bean>
<!--unity resource server filter-->
<oauth2:resource-server id="unityResourceServer" resource-id="unity-resource" token-services-ref="tokenServices"/>
<beans:bean id="clientDetailsService" class="com.free.ac.oauth.common.CustomJdbcClientDetailsService">
<beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>
<!--Config token services-->
<beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.JdbcTokenStore">
<beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>
<beans:bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<beans:property name="tokenStore" ref="tokenStore"/>
<beans:property name="clientDetailsService" ref="clientDetailsService"/>
<beans:property name="supportRefreshToken" value="true"/>
</beans:bean>
<beans:bean id="oauthUserApprovalHandler" class="com.free.ac.oauth.common.OauthUserApprovalHandler">
<beans:property name="tokenServices" ref="tokenServices"/>
<beans:property name="oauthService" ref="oauthService"/>
</beans:bean>
<beans:bean id="jdbcAuthorizationCodeServices" class="org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices">
<beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>
<oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
user-approval-handler-ref="oauthUserApprovalHandler" user-approval-page="ac/portal/oauth_approval.jsp" error-page="ac/portal/oauth_error.jsp">
<oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices"/>
<oauth2:implicit/>
<oauth2:refresh-token/>
<oauth2:client-credentials/>
<oauth2:password/>
</oauth2:authorization-server>
<beans:bean id="oauth2AuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
<beans:bean id="oauth2ClientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<beans:constructor-arg ref="clientDetailsService"/>
</beans:bean>
<authentication-manager id="oauth2AuthenticationManager">
<authentication-provider user-service-ref="oauth2ClientDetailsUserService"/>
</authentication-manager>
<beans:bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/>
<beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="oauth2AccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="acUserDetailsService">
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
<!-- session管理配置 -->
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
<beans:bean id="sessionStrategy" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:property name="maximumSessions" value="1"></beans:property>
<beans:property name="exceptionIfMaximumExceeded" value="false"></beans:property>
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry"></beans:constructor-arg>
</beans:bean>
</beans:beans>
说明:
oauth2AccessDeniedHandler:自己编写的类,继承TokenServicesUserApprovalHandler,处理是否需要授权。
CustomJdbcClientDetailsService:自定义类,继承JdbcClientDetailsService,处理认证客户端
oauth_approval.jsp:授权成功页
oauth_error.jsp:授权失败页
acUserDetailsService:继承UserDetailsService,是中心提供的用户API接口