Spring Security 3.1 自定义 authentication provider
2014-12-04 19:44
417 查看
前言
在使用Spring Security的时候,遇到一个比较特殊的情况,需要根据用户名、邮箱等多个条件去验证用户或者使用第三方的验证服务来进行用户名和密码的判断,这样SS(Spring Security,一下简称SS)内置的authentication provider和user detail service就不能用了,花了一些时间去寻找其他的办法。前置条件
Spring MVC 结构的Web项目Spring Security
使用第三方的Service验证用户名密码(并非数据库或者OpenID等SS已经支持的服务)
需求
根据用户输入的用户名和密码验证登录问题分析
在尝试了修改Filter、替换SS内置的Filter之后,发现了一个比较简单的方法,这里简单的讲讲思路。先看看SS验证用户名和密码的过程:
DelegatingFilterProxy(Security filter chain):
ConcurrentSessionFilter
SecurityContextPersistenceFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
这些都是内置的Filter,请求会从上往下依次过滤。这里由于我们主要关心用户名和密码的验证,所以就要从UsernamePasswordAuthenticationFilter下手了。(UsernamePasswordAuthenticationFilter需要AuthenticationManager去进行验证。)
在SS的配置文件里面可以看到,如何给SS传递用户验证信息数据源(设置AuthenticationManager):
1 2 3 4 5 6 7 | <authentication-manager> <authentication-provider> <user-service> <username="admin"authorities="ROLE_USER"password="admin"/> </user-service> </authentication-provider> </authentication-manager> |
内置的AuthenticationManager为org.springframework.security.authentication.ProviderManager
默认的AuthenticationProvider为org.springframework.security.authentication.dao.DaoAuthenticationProvider
再往下看,这个authentication provider使用org.springframework.security.core.userdetails.UserDetailsService去进行验证
到这里用过SS的都清楚了,只需要实现一个UserDetailService,写写下面这个方法就OK了:
1 | UserDetails loadUserByUsername(String username)throwsUsernameNotFoundException; |
这个方法只有一个参数,怎么才能传递多个参数呢?
一个可用的解决方案
重新自定义一个authentication provider,替换掉默认的DaoAuthenticationProvider.先看看XML的配置:
1 2 3 4 5 6 7 8 9 | <authentication-manageralias="authenticationManager"> <authentication-providerref="loginAuthenticationProvider"> </authentication-provider> </authentication-manager> <beanid="loginAuthenticationProvider" class="com.XXX.examples.security.LoginAuthenticationProvider"> <propertyname="userDetailsService"ref="loginUserDetailService"></property> </bean> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | publicclassLoginAuthenticationProviderextendsAbstractUserDetailsAuthenticationProvider { // ~ Instance fields // ================================================================================================ privatePasswordEncoder passwordEncoder =newPlaintextPasswordEncoder(); privateSaltSource saltSource; privateLoginUserDetailsService userDetailsService; // ~ Methods // ======================================================================================================== protectedvoidadditionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throwsAuthenticationException { Object salt =null; if(this.saltSource !=null) { salt =this.saltSource.getSalt(userDetails); } if(authentication.getCredentials() ==null) { logger.debug("Authentication failed: no credentials provided"); thrownewBadCredentialsException("Bad credentials:"+ userDetails); } String presentedPassword = authentication.getCredentials().toString(); if(!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug("Authentication failed: password does not match stored value"); thrownewBadCredentialsException("Bad credentials:"+ userDetails); } } protectedvoiddoAfterPropertiesSet()throwsException { Assert.notNull(this.userDetailsService,"A UserDetailsService must be set"); } protectedPasswordEncoder getPasswordEncoder() { returnpasswordEncoder; } protectedSaltSource getSaltSource() { returnsaltSource; } protectedLoginUserDetailsService getUserDetailsService() { returnuserDetailsService; } protectedfinalUserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throwsAuthenticationException { UserDetails loadedUser; try { String password = (String) authentication.getCredentials(); loadedUser = getUserDetailsService().loadUserByUsername(username, password);//区别在这里 } catch(UsernameNotFoundException notFound) { thrownotFound; } catch(Exception repositoryProblem) { thrownewAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } if(loadedUser ==null) { thrownewAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } returnloadedUser; } /** * Sets the PasswordEncoder instance to be used to encode and validate * passwords. If not set, the password will be compared as plain text. * <p> * For systems which are already using salted password which are encoded * with a previous release, the encoder should be of type * { @code org.springframework.security.authentication.encoding.PasswordEncoder} * . Otherwise, the recommended approach is to use * { @code org.springframework.security.crypto.password.PasswordEncoder}. * * @param passwordEncoder * must be an instance of one of the { @code PasswordEncoder} * types. */ publicvoidsetPasswordEncoder(Object passwordEncoder) { Assert.notNull(passwordEncoder,"passwordEncoder cannot be null"); if(passwordEncoderinstanceofPasswordEncoder) { this.passwordEncoder = (PasswordEncoder) passwordEncoder; return; } if(passwordEncoderinstanceoforg.springframework.security.crypto.password.PasswordEncoder) { finalorg.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder) passwordEncoder; this.passwordEncoder =newPasswordEncoder() { privatevoidcheckSalt(Object salt) { Assert.isNull(salt,"Salt value must be null when used with crypto module PasswordEncoder"); } publicString encodePassword(String rawPass, Object salt) { checkSalt(salt); returndelegate.encode(rawPass); } publicbooleanisPasswordValid(String encPass, String rawPass, Object salt) { checkSalt(salt); returndelegate.matches(rawPass, encPass); } }; return; } thrownewIllegalArgumentException("passwordEncoder must be a PasswordEncoder instance"); } /** * The source of salts to use when decoding passwords. <code>null</code> is * a valid value, meaning the <code>DaoAuthenticationProvider</code> will * present <code>null</code> to the relevant <code>PasswordEncoder</code>. * <p> * Instead, it is recommended that you use an encoder which uses a random * salt and combines it with the password field. This is the default * approach taken in the * { @code org.springframework.security.crypto.password} package. * * @param saltSource * to use when attempting to decode passwords via the * <code>PasswordEncoder</code> */ publicvoidsetSaltSource(SaltSource saltSource) { this.saltSource = saltSource; } publicvoidsetUserDetailsService(LoginUserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | publicinterfaceLoginUserDetailsService { /** * 功能描述:根据用户米密码验证用户信息 * <p> * 前置条件: * <p> * 方法影响: * <p> * Author , 2012-9-26 * * @since server 2.0 * @param username * @param password * @return * @throws UsernameNotFoundException */ UserDetails loadUserByUsername(String username, String password)throwsUsernameNotFoundException; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | publicclassLoginUserDetailsServiceImplimplementsLoginUserDetailsService { privateUserAccountService userAccountService; /** * */ publicLoginUserDetailsServiceImpl() { } /** * getter method * * @see LoginUserDetailsServiceImpl#userAccountService * @return the userAccountService */ publicUserAccountService getUserAccountService() { returnuserAccountService; } /** * 功能描述:查找登录的用户 * <p> * 前置条件: * <p> * 方法影响: * <p> * Author , 2012-9-26 * * @since server 2.0 * @param username * @return */ publicUserDetails loadUserByUsername(String username, String password)throwsUsernameNotFoundException { booleanresult = userAccountService.validate(username, password); if(!result) { returnnull; } List authorities =newArrayList(); GrantedAuthorityImpl grantedAuthorityImpl =newGrantedAuthorityImpl(); authorities.add(grantedAuthorityImpl); LoginUserDetailsImpl user =newLoginUserDetailsImpl(username, password, authorities); grantedAuthorityImpl.setDelegate(user); } /** * setter method * * @see LoginUserDetailsServiceImpl#userAccountService * @param userAccountService * the userAccountService to set */ publicvoidsetUserAccountService(UserAccountService userAccountService) { this.userAccountService = userAccountService; } } |
GrantedAuthorityImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | publicclassGrantedAuthorityImplimplementsGrantedAuthority { /** * ROLE USER 权限 */ privatestaticfinalString ROLE_USER ="ROLE_USER"; /** * Serial version UID */ privatestaticfinallongserialVersionUID = 1L; privateUserDetails delegate; publicGrantedAuthorityImpl(UserDetails user) { this.delegate = user; } /** * */ publicGrantedAuthorityImpl() { } publicString getAuthority() { returnROLE_USER; } /** * setter method * * @see GrantedAuthorityImpl#delegate * @param delegate * the delegate to set */ publicvoidsetDelegate(UserDetails delegate) { this.delegate = delegate; } /** * getter method * * @see GrantedAuthorityImpl#delegate * @return the delegate */ publicUserDetails getDelegate() { returndelegate; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | publicclassLoginUserDetailsImplextendsUserimplementsUserDetails { /** * */ privatestaticfinallongserialVersionUID = -5424897749887458053L; /** * 邮箱 */ privateString mail; /** * @param username * @param password * @param enabled * @param accountNonExpired * @param credentialsNonExpired * @param accountNonLocked * @param authorities */ publicLoginUserDetailsImpl(String username, String password,booleanenabled,booleanaccountNonExpired, booleancredentialsNonExpired,booleanaccountNonLocked, Collection<?extendsGrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); } /** * @param username * @param password * @param authorities */ publicLoginUserDetailsImpl(String username, String password, Collection<?extendsGrantedAuthority> authorities) { super(username, password, authorities); } /** * @param username * @param password * @param authorities */ publicLoginUserDetailsImpl(String username, String password) { super(username, password,newArrayList<GrantedAuthority>()); } /** * getter method * @see LoginUserDetailsImpl#mail * @return the mail */ publicString getMail() { returnmail; } /** * setter method * @see LoginUserDetailsImpl#mail * @param mail the mail to set */ publicvoidsetMail(String mail) { this.mail = mail; } @Override publicString toString() { returnsuper.toString() +"; Mail: "+ mail; } } |
1 2 | <beanid="userAccountService" class="com.XXX.UserAccountService"/> |
小结
为了自定义一个authenticationProvider,我们还需要自定义一个UserDetailsService,只需要这2个类,就实现了对验证参数的扩展了。。。相关文章推荐
- Spring Security 3.1自定义登录
- spring security 3.1中基于数据库自定义验证授权功能实现
- Spring Security 3.1 自定义实例之登陆
- 转:Spring Security 3.1 自定义实例之登陆
- spring security 3.1中基于数据库自定义验证授权功能实现
- Spring Security 3.1 自定义 authentication provider
- Spring Security 3.1 自定义 authentication provider
- Spring Security3.1 最新配置实例
- Spring Security3.1 最新配置实例
- spring security3.1 不再支持 filters="none" 解决办法
- Spring Security 3.1 中功能强大的加密工具 PasswordEncoder
- spring security自定义认证登录的全过程记录
- Spring Security3.1 最新配置实例
- spring security进级到3.1不再支持 filters="none"
- 【翻译】通过CMD3.1来实现自定义基于海王星主题的Ext JS 4.1应用程序
- Spring Security系列四 自定义决策管理器(动态权限码)
- Spring Security 自定义登陆页面报HTTP Status 403 - Invalid CSRF Token 'null' was found on the request paramet
- Spring Security3.1 最新配置实例 .
- Spring Security教程(10)---- 自定义登录成功后的处理程序及修改默认验证地址
- spring security 扩展User,自定义User字段