您的位置:首页 > 编程语言 > Java开发

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>

这里我们依然仿照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个类,就实现了对验证参数的扩展了。。。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: