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> <!-- ======================== 单点登录结束 ======================== -->