码迷,mamicode.com
首页 > 编程语言 > 详细

Spring Security 3.1 自定义 authentication provider

时间:2014-12-04 20:07:37      阅读:388      评论:0      收藏:0      [点我收藏+]

标签:style   http   io   ar   color   os   使用   sp   for   

前言

在使用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):

  1.   ConcurrentSessionFilter
  2.   SecurityContextPersistenceFilter
  3.   LogoutFilter
  4.   UsernamePasswordAuthenticationFilter
  5.   RequestCacheAwareFilter
  6.   SecurityContextHolderAwareRequestFilter
  7.   RememberMeAuthenticationFilter
  8.   AnonymousAuthenticationFilter
  9.   SessionManagementFilter
  10.   ExceptionTranslationFilter
  11.   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>
这里我们依然仿照DaoAuthenticationProvider,传递一个UserDetailService给它,下面看看其实现:
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;
    }
}
代码跟DaoAuthenticationProvider几乎一样,只是我们替换了UserDetailService,使用自定义的一个新的LoginUserDetailService:
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;
}
一个简单的实现LoginUserDetailsServiceImpl:
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;
    }
 
}
LoginUserDetailsImpl
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 自定义 authentication provider

标签:style   http   io   ar   color   os   使用   sp   for   

原文地址:http://my.oschina.net/bigyuan/blog/352408

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!