package com.gwtjs.sso.server;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.jdbc.core.JdbcTemplate;
/**
*
* <h2>传送更多用户信息</h2>
* <p>
* 如果是默认配置,只能传输用户名到客户端,现希望可以传送更多的信息给客户端,例如,用户拥有的权限信息,可以传给客户
* </p>
* <pre>
* 参考: * </pre>
* @author gwtjs.com
*
*/
public class BaseCredentialsToPrincipalResolver implements
CredentialsToPrincipalResolver {
//private static final Logger logger = Log4jLoggerFactory.getLogger(BaseCredentialsToPrincipalResolver.class);
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Principal resolvePrincipal(Credentials credentials) {
UsernamePasswordCredentials up = (UsernamePasswordCredentials) credentials;
// 获取登录帐户
//logger.debug("登录用户:" + up.getUsername());
// System.out.println(up.getPassword());
final Map<String, Object> attr = new HashMap<String, Object>();
// ,USER_NAME,ENABLED,ISSYS
String sql = "SELECT USER_ACCOUNT username from SYS_USERS where ENABLED = 1 and USER_ACCOUNT ='"
+ up.getUsername() + "'";
List<String> list = jdbcTemplate.queryForList(sql, String.class);
attr.put(up.getUsername(), list);
Principal p = new SimplePrincipal(up.getUsername(), attr);
return p;
}
public boolean supports(Credentials credentials) {
return credentials != null
&& UsernamePasswordCredentials.class
.isAssignableFrom(credentials.getClass());
}
}
package com.gwtjs.sso.server;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import java.util.HashSet;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import com.gwtjs.sso.server.model.BaseUser;
import com.gwtjs.sso.server.model.BaseUserDetail;
/**
* 登陆时使用的工具
*/
public class UsernamePasswordJDBCAuthenticationHandler extends
AbstractUsernamePasswordAuthenticationHandler {
private JdbcTemplate jdbcTemplate;
private PasswordEncoder passEncoder;
private SaltSource saltSource;
/**
* 登陆时使用的方法
*/
protected boolean authenticateUsernamePasswordInternal(
UsernamePasswordCredentials credentials)
throws AuthenticationException {
final String username = credentials.getUsername();
final String password = credentials.getPassword();
System.out.println("username:" + username + " --> password:"
+ password);
String sql = "select USER_ID,USER_ACCOUNT,USER_NAME,USER_PASSWORD from SYS_USERS where ENABLED = 1 and USER_ACCOUNT ='"
+ username + "'";
BaseUser baseUser = jdbcTemplate.queryForObject(sql, null,
new RowMapper<baseuser>() {
@Override
public BaseUser mapRow(ResultSet rs, int rowNum)
throws SQLException {
BaseUser baseuser = new BaseUser();
baseuser.setCode(rs.getString("USER_ID"));
baseuser.setUserAccount(rs.getString("USER_ACCOUNT"));
baseuser.setUserPassword(rs.getString("USER_PASSWORD"));
baseuser.setUserName(rs.getString("USER_NAME"));
return baseuser;
}
});
Set<grantedauthority> auth = new HashSet<grantedauthority>();
GrantedAuthority ga = (GrantedAuthority) new SimpleGrantedAuthority("admin");
auth.add(ga);
/*
BaseUserDetail(String userAccount, String username, String userPassword, String code, boolean enabled, boolean issys, boolean accountNonExpired, boolean accountNonLocked, Set<grantedauthority> auth)
*/
BaseUserDetail user = new BaseUserDetail(username,
baseUser.getUserName(), baseUser.getUserPassword(),
baseUser.getCode(), true, true, true, true, auth);
System.out.println(user);
if (user != null) {
//System.out.println("saltSource.getSalt(user) ... "+ saltSource.getSalt(user));
// 验证密码
/*String encodePassword = this.passEncoder.encodePassword(password,
this.saltSource.getSalt(user));*/
String encodePassword = this.passEncoder.encodePassword(password, user.getUserAccount());
System.out.println("password ... " + password);
System.out.println("UserAccount ... " + user.getUserAccount());
System.out.println("username ... " + username);
System.out.println("encodePassword ... " + encodePassword);
System.out.println("user.getPassword ... " + user.getPassword());
if (encodePassword.equals(user.getPassword())) {
return true;
}
}
return false;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setPassEncoder(PasswordEncoder passEncoder) {
this.passEncoder = passEncoder;
}
public void setSaltSource(SaltSource saltSource) {
this.saltSource = saltSource;
}
}
MD%加密算法有问题:
String encodePassword = this.passEncoder.encodePassword(user.getUserAccount(), password);
username:dzg --> password:dzg1
encodePassword ... 042c1c7be2a9a18f96be3b2169d663a6
username:dzg --> password:dzg1
encodePassword ... 042c1c7be2a9a18f96be3b2169d663a6
String encodePassword = this.passEncoder.encodePassword( password,user.getUserAccount());
username:dzg --> password:dzg1
encodePassword ... c2ae6fdc2054ae785d5482d1270904b4
数据库结果:
encodePassword ... C857A25F749F1FE0A28427AFE853C4F8
username:dzg --> password:dzg1
BaseUserDetail [
userId=dzg4, userAccount=dzg, username=董正光,
userPassword=C857A25F749F1FE0A28427AFE853C4F8, userDesc=null, enabled=true,
issys=true,
userDept=null, userDuty=null, password=null, authorities=[admin],
accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=false]
password ... dzg1
UserAccount ... dzg
username ... dzg
encodePassword ... c2ae6fdc2054ae785d5482d1270904b4
user.getPassword ... null
cas/web/WEB-INF/deployerConfigContext.xml
完整的配置文件,security使用完全的数据验证,cas使用数据验证;
<?xml version="1.0" encoding="UTF-8"?>
<!-- | deployerConfigContext.xml centralizes into one file some of the declarative
configuration that | all CAS deployers will need to modify. | | This file
declares some of the Spring-managed JavaBeans that make up a CAS deployment.
| The beans declared in this file are instantiated at context initialization
time by the Spring | ContextLoaderListener declared in web.xml. It finds
this file because this | file is among those declared in the context parameter
"contextConfigLocation". | | By far the most common change you will need
to make in this file is to change the last bean | declaration to replace
the default SimpleTestUsernamePasswordAuthenticationHandler with | one implementing
your approach for authenticating usernames and passwords. + -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl">
<property name="credentialsToPrincipalResolvers">
<list>
<!-- 此处修改了CredentialToPrincipal,因为除了要传递用户信息之处,还要传递一部分权限信息 若无此需求,可使用UsernamePasswordCredentialsToPrincipalResolver(系统默认) -->
<bean
class="com.gwtjs.hacom.vgop.security.cas.BaseUsernamePasswordCredentialsToPrincipalResolver"
p:jdbcTemplate-ref="jdbcTemplate">
<property name="attributeRepository" ref="attributeRepository" />
</bean>
<bean
class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
</list>
</property>
<property name="authenticationHandlers">
<list>
<bean
class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient"><!-- p:requireSecure="true" -->
<property name="requireSecure" value="false" />
</bean>
<!-- 使用我们自己的验证方式 -->
<bean
class="com.gwtjs.hacom.vgop.security.cas.UsernamePasswordJDBCAuthenticationHandler"
p:jdbcTemplate-ref="jdbcTemplate" p:passEncoder-ref="passwordEncoder">
<!-- p:saltSource-ref="saltSource" -->
</bean>
</list>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
<property name="username" value="FrameworkTest" />
<property name="password" value="FrameworkTest" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<!-- autowire="byName" -->
<bean id="passwordEncoder"
class="org.springframework.security.authentication.encoding.Md5PasswordEncoder">
<!-- <constructor-arg index="0"> <value>MD5</value> </constructor-arg> -->
</bean>
<sec:user-service id="userDetailsService">
<sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused"
authorities="ROLE_ADMIN" />
</sec:user-service><!-- 角色权限更多信息可在此关联,使用SingleRowJdbcPersonAttributeDao
获取更多用户的信息 -->
<bean id="attributeRepository"
class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">
<constructor-arg index="0" ref="dataSource" />
<constructor-arg index="1"
value="select USER_ID USERID,USER_ACCOUNT ACCOUNT,USER_PASSWORD PASSWORD from sys_users where USER_ACCOUNT = ?" />
<!--这里的key需写username,value对应数据库用户名字段 -->
<property name="queryAttributeMapping">
<map>
<entry key="username" value="USERID" />
</map>
</property>
<!--key对应数据库字段,value对应客户端获取参数 -->
<property name="resultAttributeMapping">
<map>
<!--这个从数据库中获取的角色,用于在应用中security的权限验证 -> key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值 -->
<entry key="UID" value="userId" />
<entry key="userAccount" value="authorities" />
<entry key="userAccount" value="username" />
<entry key="PASSWORD" value="userPassword" />
</map>
</property>
</bean>
<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
</bean>
<bean id="auditTrailManager"
class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" />
</beans>
client applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans" 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-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<!-- entry-point-ref="casEntryPoint"cas切入点 -->
<http auto-config="true" access-denied-page="/accessDenied.jsp"
entry-point-ref="casEntryPoint">
<intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<!-- 不要过滤图片等静态资源 -->
<intercept-url pattern="/**/*.jpg" filters="none" />
<intercept-url pattern="/**/*.png" filters="none" />
<intercept-url pattern="/**/*.gif" filters="none" />
<intercept-url pattern="/**/*.css" filters="none" />
<intercept-url pattern="/**/*.js" filters="none" />
<intercept-url pattern="/login.action" filters="none" />
<intercept-url pattern="/UserTestLoginServlet" filters="none" />
<intercept-url pattern="/security-flash" />
<!-- 登录页面和忘记密码页面不过滤 --><!-- 先前的登陆不需要了 -->
<!-- <intercept-url pattern="/login.jsp" filters="none" /> -->
<intercept-url pattern="/forgotpassword.jsp" filters="none" />
<!-- <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true"
default-target-url="/index.jsp" /> -->
<!-- "记住我"功能,采用持久化策略(将用户的登录信息存放在数据库表中) -->
<remember-me data-source-ref="dataSource" /><!-- Uncomment
to limit the number of sessions a user can have 检测失效的sessionId,超时时定位到另外一个URL -->
<session-management invalid-session-url="/sessionTimeout.jsp">
<!-- max-sessions是设置单个用户最大并行会话数; error-if-maximum-exceeded是配置当用户登录数达到最大时是否报错,
设置为true时会报错且后登录的会话不能登录 -->
<concurrency-control max-sessions="3"
error-if-maximum-exceeded="true" />
</session-management>
<!-- webservices验证 -->
<http-basic />
<!-- CAS过滤器链放在基础过滤器前面 -->
<custom-filter position="CAS_FILTER" ref="casFilter" />
<!-- 增加一个自定义的filter,放在FILTER_SECURITY_INTERCEPTOR之前, 实现用户、角色、权限、资源的数据库管理。
11/3/23 -->
<custom-filter ref="customFilter" before="FILTER_SECURITY_INTERCEPTOR" />
<!-- 来宾用户名 -->
<anonymous username="Guest" />
<!-- <logout logout-success-url="/cas-logout.jsp" /> -->
<!-- 单点登陆 过滤器 -->
<custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER" />
<!-- 单点登陆退出过滤器 -->
<custom-filter ref="singleLogoutFilter" before="CAS_FILTER" />
</http>
<b:bean id="customFilter"
class="com.huawei.hacmp.security.CustomFilterSecurityInterceptor">
<b:property name="authenticationManager" ref="authenticationManager" />
<b:property name="accessDecisionManager" ref="customAccessDecisionManager" />
<b:property name="securityMetadataSource" ref="customSecurityMetadataSource" />
</b:bean>
<!-- 注意能够为authentication-manager 设置alias别名 --><!-- 启用单点登陆后此认证废弃 -->
<!-- <authentication-manager alias="authenticationManager"> <authentication-provider
user-service-ref="userDetailsManager"> <password-encoder ref="passwordEncoder">
<salt-source user-property="username" /> </password-encoder> </authentication-provider>
</authentication-manager> -->
<!-- 用户详细信息管理:数据源、用户缓存(通过数据库管理用户、角色、权限、资源)。(新版本) 11/3/23 在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<b:bean id="userDetailsManager" class="com.huawei.hacmp.security.CustomUserDetailsService">
<b:property name="sysUsersDao" ref="sysUsersDao" />
</b:bean><!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源。11/3/23 -->
<b:bean id="customAccessDecisionManager"
class="com.huawei.hacmp.security.CustomAccessDecisionManager">
</b:bean>
<!-- 资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色去访问。11/3/23 -->
<b:bean id="customSecurityMetadataSource"
class="com.huawei.hacmp.security.CustomInvocationSecurityMetadataSourceService">
</b:bean>
<!-- =====================单点登陆===================== -->
<!-- cas中心认证服务入口 -->
<b:bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<b:property name="loginUrl" value="https://sso.vgop.huawei.com:8443/login" />
<b:property name="serviceProperties" ref="serviceProperties" />
</b:bean><!-- 单点登陆服务属性 -->
<b:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
<!-- cas中心认证服务配置,登录成功后的返回地址 -->
<b:property name="service"
value="http://sso.vgop.huawei.com:8181/framework/j_spring_cas_security_check" />
<!-- 根据需要启用此参数,当url传递renew参数并且为true时,无论用户有无认证cookie都会强制进行验证 -->
<b:property name="sendRenew" value="false" />
</b:bean>
<!-- <b:bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
<b:property name="authenticationManager" ref="authenticationManager" /> </b:bean> -->
<!-- CAS service ticket(中心认证服务凭据)验证 -->
<b:bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
<b:property name="authenticationManager" ref="authenticationManager" />
<!-- <b:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"
/> <b:property name="authenticationFailureHandler" ref="authenticationFailureHandler"
/> -->
</b:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="casAuthenticationProvider" />
</authentication-manager>
<b:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<b:property name="authenticationUserDetailsService">
<b:bean
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<b:constructor-arg ref="userDetailsManager" />
</b:bean>
</b:property>
<b:property name="serviceProperties" ref="serviceProperties" />
<b:property name="ticketValidator">
<b:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<b:constructor-arg index="0"
value="https://sso.vgop.huawei.com:8443/" />
</b:bean>
</b:property>
<b:property name="key" value="authorities" />
</b:bean>
<b:bean id="casAuthenticationUserDetailsService"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<b:property name="userDetailsService" ref="userDetailsManager" />
</b:bean>
<b:bean id="requestSingleLogoutFilter"
class="org.springframework.security.web.authentication.logout.LogoutFilter">
<b:constructor-arg value="https://sso.vgop.huawei.com:8443/logout" />
<b:constructor-arg>
<b:bean
class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
</b:constructor-arg>
<b:property name="filterProcessesUrl" value="/j_spring_cas_security_logout" />
</b:bean>
<b:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />
</b:beans>
web.xml
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 该过滤器用于实现单点登出功能,可选配置。 -->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
<filter-name>CASFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://sso.gwtjs.com:8443/cas/login</param-value>
</init-param>
<init-param>
<!--这里的server是服务端的IP-->
<param-name>serverName</param-name>
<param-value>http://localhost:10000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://sso.gwtjs.com:8443/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:10000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
该过滤器负责实现HttpServletRequest请求的包裹,
比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>
org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
比如AssertionHolder.getAssertion().getPrincipal().getName()。
-->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 自动根据单点登录的结果设置本系统的用户信息 -->
<filter>
<display-name>AutoSetUserAdapterFilter</display-name>
<filter-name>AutoSetUserAdapterFilter</filter-name>
<filter-class>com.gwtjs.demo.filter.AutoSetUserAdapterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AutoSetUserAdapterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ======================== 单点登录结束 ======================== -->

本文介绍了一种基于CAS协议实现的单点登录(SSO)系统,并详细讲解了其核心组件之一——用户认证模块的设计与实现过程。该系统不仅实现了用户的身份验证,还能够传递额外的用户权限信息。
4675

被折叠的 条评论
为什么被折叠?



