javascript
Spring Security 3.1 自定义 authentication provider
來源:http://www.xeclipse.com/?p=1359
前言
在使用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):
這些都是內置的Filter,請求會從上往下依次過濾。這里由于我們主要關心用戶名和密碼的驗證,所以就要從UsernamePasswordAuthenticationFilter下手了。(UsernamePasswordAuthenticationFilter需要AuthenticationManager去進行驗證。)
在SS的配置文件里面可以看到,如何給SS傳遞用戶驗證信息數據源(設置AuthenticationManager):
| 1 2 3 4 5 6 7 | <authentication-manager> ????<authentication-provider> ????????<user-service> ????????????<user name="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) throws UsernameNotFoundException; |
一個可用的解決方案
重新自定義一個authentication provider,替換掉默認的DaoAuthenticationProvider. 先看看XML的配置:| 1 2 3 4 5 6 7 8 9 | <authentication-manager alias="authenticationManager"> ????<authentication-provider ref="loginAuthenticationProvider"> ????</authentication-provider> </authentication-manager> <bean id="loginAuthenticationProvider" ????class="com.XXX.examples.security.LoginAuthenticationProvider"> ????<property name="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 | public class LoginAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { ????// ~ Instance fields ????// ================================================================================================ ????private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder(); ????private SaltSource saltSource; ????private LoginUserDetailsService userDetailsService; ????// ~ Methods ????// ======================================================================================================== ????protected void additionalAuthenticationChecks(UserDetails userDetails, ??????????????????????????????????????????????????UsernamePasswordAuthenticationToken authentication) ????????????throws AuthenticationException ????{ ????????Object salt = null; ????????if (this.saltSource != null) ????????{ ????????????salt = this.saltSource.getSalt(userDetails); ????????} ????????if (authentication.getCredentials() == null) ????????{ ????????????logger.debug("Authentication failed: no credentials provided"); ????????????throw new BadCredentialsException("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"); ????????????throw new BadCredentialsException("Bad credentials:" + userDetails); ????????} ????} ????protected void doAfterPropertiesSet() throws Exception ????{ ????????Assert.notNull(this.userDetailsService, "A UserDetailsService must be set"); ????} ????protected PasswordEncoder getPasswordEncoder() ????{ ????????return passwordEncoder; ????} ????protected SaltSource getSaltSource() ????{ ????????return saltSource; ????} ????protected LoginUserDetailsService getUserDetailsService() ????{ ????????return userDetailsService; ????} ????protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) ????????????throws AuthenticationException ????{ ????????UserDetails loadedUser; ????????try ????????{ ????????????String password = (String) authentication.getCredentials(); ????????????loadedUser = getUserDetailsService().loadUserByUsername(username, password);//區別在這里 ????????} ????????catch (UsernameNotFoundException notFound) ????????{ ????????????throw notFound; ????????} ????????catch (Exception repositoryProblem) ????????{ ????????????throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); ????????} ????????if (loadedUser == null) ????????{ ????????????throw new AuthenticationServiceException( ?????????????????????????????????????????????????????"UserDetailsService returned null, which is an interface contract violation"); ????????} ????????return loadedUser; ????} ????/** ?????* 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. ?????*/ ????public void setPasswordEncoder(Object passwordEncoder) ????{ ????????Assert.notNull(passwordEncoder, "passwordEncoder cannot be null"); ????????if (passwordEncoder instanceof PasswordEncoder) ????????{ ????????????this.passwordEncoder = (PasswordEncoder) passwordEncoder; ????????????return; ????????} ????????if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) ????????{ ????????????final org.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder) passwordEncoder; ????????????this.passwordEncoder = new PasswordEncoder() ????????????{ ????????????????private void checkSalt(Object salt) ????????????????{ ????????????????????Assert.isNull(salt, "Salt value must be null when used with crypto module PasswordEncoder"); ????????????????} ????????????????public String encodePassword(String rawPass, Object salt) ????????????????{ ????????????????????checkSalt(salt); ????????????????????return delegate.encode(rawPass); ????????????????} ????????????????public boolean isPasswordValid(String encPass, String rawPass, Object salt) ????????????????{ ????????????????????checkSalt(salt); ????????????????????return delegate.matches(rawPass, encPass); ????????????????} ????????????}; ????????????return; ????????} ????????throw new IllegalArgumentException("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> ?????*/ ????public void setSaltSource(SaltSource saltSource) ????{ ????????this.saltSource = saltSource; ????} ????public void setUserDetailsService(LoginUserDetailsService userDetailsService) ????{ ????????this.userDetailsService = userDetailsService; ????} } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public interface LoginUserDetailsService { ????/** ?????* 功能描述:根據用戶米密碼驗證用戶信息 ?????* <p> ?????* 前置條件: ?????* <p> ?????* 方法影響: ?????* <p> ?????* Author , 2012-9-26 ?????* ?????* @since server 2.0 ?????* @param username ?????* @param password ?????* @return ?????* @throws UsernameNotFoundException ?????*/ ????UserDetails loadUserByUsername(String username, String password) throws UsernameNotFoundException; } |
| 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 | public class LoginUserDetailsServiceImpl implements LoginUserDetailsService { ????private UserAccountService userAccountService; ????/** ?????* ?????*/ ????public LoginUserDetailsServiceImpl() ????{ ????} ????/** ?????* getter method ?????* ?????* @see LoginUserDetailsServiceImpl#userAccountService ?????* @return the userAccountService ?????*/ ????public UserAccountService getUserAccountService() ????{ ????????return userAccountService; ????} ????/** ?????* 功能描述:查找登錄的用戶 ?????* <p> ?????* 前置條件: ?????* <p> ?????* 方法影響: ?????* <p> ?????* Author , 2012-9-26 ?????* ?????* @since server 2.0 ?????* @param username ?????* @return ?????*/ ????public UserDetails loadUserByUsername(String username, String password) throws UsernameNotFoundException ????{ ????????boolean result = userAccountService.validate(username, password); ????????if (!result) ????????{ ????????????return null; ????????} ????????List authorities = new ArrayList(); ????????GrantedAuthorityImpl grantedAuthorityImpl = new GrantedAuthorityImpl(); ????????authorities.add(grantedAuthorityImpl); ????????LoginUserDetailsImpl user = new LoginUserDetailsImpl(username, password, authorities); ????????grantedAuthorityImpl.setDelegate(user); ????} ????/** ?????* setter method ?????* ?????* @see LoginUserDetailsServiceImpl#userAccountService ?????* @param userAccountService ?????*??????????? the userAccountService to set ?????*/ ????public void setUserAccountService(UserAccountService userAccountService) ????{ ????????this.userAccountService = userAccountService; ????} } |
| 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 | public class GrantedAuthorityImpl implements GrantedAuthority { ????/** ?????* ROLE USER 權限 ?????*/ ????private static final String ROLE_USER = "ROLE_USER"; ????/** ?????* Serial version UID ?????*/ ????private static final long serialVersionUID = 1L; ????private UserDetails delegate; ????public GrantedAuthorityImpl(UserDetails user) ????{ ????????this.delegate = user; ????} ????/** ?????* ?????*/ ????public GrantedAuthorityImpl() ????{ ????} ????public String getAuthority() ????{ ????????return ROLE_USER; ????} ????/** ?????* setter method ?????* ?????* @see GrantedAuthorityImpl#delegate ?????* @param delegate ?????*??????????? the delegate to set ?????*/ ????public void setDelegate(UserDetails delegate) ????{ ????????this.delegate = delegate; ????} ????/** ?????* getter method ?????* ?????* @see GrantedAuthorityImpl#delegate ?????* @return the delegate ?????*/ ????public UserDetails getDelegate() ????{ ????????return delegate; ????} } |
| 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 | public class LoginUserDetailsImpl extends User implements UserDetails { ????/** ?????* ?????*/ ????private static final long serialVersionUID = -5424897749887458053L; ????/** ?????* 郵箱 ?????*/ ????private String mail; ????/** ?????* @param username ?????* @param password ?????* @param enabled ?????* @param accountNonExpired ?????* @param credentialsNonExpired ?????* @param accountNonLocked ?????* @param authorities ?????*/ ????public LoginUserDetailsImpl(String username, String password, boolean enabled, boolean accountNonExpired, ????????????????????????????????boolean credentialsNonExpired, boolean accountNonLocked, ????????????????????????????????Collection<? extends GrantedAuthority> authorities) ????{ ????????super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); ????} ????/** ?????* @param username ?????* @param password ?????* @param authorities ?????*/ ????public LoginUserDetailsImpl(String username, String password, Collection<? extends GrantedAuthority> authorities) ????{ ????????super(username, password, authorities); ????} ????/** ?????* @param username ?????* @param password ?????* @param authorities ?????*/ ????public LoginUserDetailsImpl(String username, String password) ????{ ????????super(username, password, new ArrayList<GrantedAuthority>()); ????} ????/** ?????* getter method ?????* @see LoginUserDetailsImpl#mail ?????* @return the mail ?????*/ ????public String getMail() ????{ ????????return mail; ????} ????/** ?????* setter method ?????* @see LoginUserDetailsImpl#mail ?????* @param mail the mail to set ?????*/ ????public void setMail(String mail) ????{ ????????this.mail = mail; ????} ????@Override ????public String toString() ????{ ????????return super.toString() + "; Mail: " + mail; ????} } |
| 1 2 | <bean id="userAccountService" ????????class="com.XXX.UserAccountService"/> |
小結
為了自定義一個authenticationProvider,我們還需要自定義一個UserDetailsService,只需要這2個類,就實現了對驗證參數的擴展了。。。
總結
以上是生活随笔為你收集整理的Spring Security 3.1 自定义 authentication provider的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基金如何计算每日收益
- 下一篇: java集合排序