spring security是一个很大的模块,本文中只涉及到了自定义参数的认证。spring security默认的验证参数只有username和password,一般来说都是不够用的。由于时间过太久,有些忘,可能有少许遗漏。好了,不废话。
spring以及spring security配置采用javaConfig,版本依次为4.2.5,4.0.4
总体思路:自定义EntryPoint,添加自定义参数扩展AuthenticationToken以及AuthenticationProvider进行验证。
首先定义EntryPoint:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class MyAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { public MyAuthenticationEntryPoint(String loginFormUrl) { super (loginFormUrl); } @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { super .commence(request, response, authException); } } |
接下来是token,validCode是验证码参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken { private String validCode; public MyUsernamePasswordAuthenticationToken(String principal, String credentials, String validCode) { super (principal, credentials); this .validCode = validCode; } public String getValidCode() { return validCode; } public void setValidCode(String validCode) { this .validCode = validCode; } } |
继续ProcessingFilter,
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
|
import com.core.shared.ValidateCodeHandle; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class MyValidCodeProcessingFilter extends AbstractAuthenticationProcessingFilter { private String usernameParam = "username" ; private String passwordParam = "password" ; private String validCodeParam = "validateCode" ; public MyValidCodeProcessingFilter() { super ( new AntPathRequestMatcher( "/user/login" , "POST" )); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String username = request.getParameter(usernameParam); String password = request.getParameter(passwordParam); String validCode = request.getParameter(validCodeParam); valid(validCode, request.getSession()); MyUsernamePasswordAuthenticationToken token = new MyUsernamePasswordAuthenticationToken(username, password, validCode); return this .getAuthenticationManager().authenticate(token); } public void valid(String validCode, HttpSession session) { if (validCode == null ) { throw new ValidCodeErrorException( "验证码为空!" ); } if (!ValidateCodeHandle.matchCode(session.getId(), validCode)) { throw new ValidCodeErrorException( "验证码错误!" ); } } } |
分别定义三个参数,用于接收login表单过来的参数,构造方法给出了login的url以及需要post方式
接下来就是认证了,此处还没到认证用户名和密码的时候,只是认证了验证码
下面是ValidateCodeHandle一个工具类以及ValidCodeErrorException:
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
|
import java.util.concurrent.ConcurrentHashMap; public class ValidateCodeHandle { private static ConcurrentHashMap validateCode = new ConcurrentHashMap<>(); public static ConcurrentHashMap getCode() { return validateCode; } public static void save(String sessionId, String code) { validateCode.put(sessionId, code); } public static String getValidateCode(String sessionId) { Object obj = validateCode.get(sessionId); if (obj != null ) { return String.valueOf(obj); } return null ; } public static boolean matchCode(String sessionId, String inputCode) { String saveCode = getValidateCode(sessionId); if (saveCode.equals(inputCode)) { return true ; } return false ; } } |
这里需要继承AuthenticationException以表明它是security的认证失败,这样才会走后续的失败流程
1
2
3
4
5
6
7
8
9
10
|
import org.springframework.security.core.AuthenticationException; public class ValidCodeErrorException extends AuthenticationException { public ValidCodeErrorException(String msg) { super (msg); } public ValidCodeErrorException(String msg, Throwable t) { super (msg, t); } } |
接下来是Provider:
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
|
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; public class MyAuthenticationProvider extends DaoAuthenticationProvider { @Override public boolean supports(Class<?> authentication) { return MyUsernamePasswordAuthenticationToken. class .isAssignableFrom(authentication); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null ; if (getSaltSource() != null ) { salt = getSaltSource().getSalt(userDetails); } if (authentication.getCredentials() == null ) { logger.debug( "Authentication failed: no credentials provided" ); throw new BadCredentialsException( "用户名或密码错误!" ); } String presentedPassword = authentication.getCredentials().toString(); if (! this .getPasswordEncoder().isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug( "Authentication failed: password does not match stored value" ); throw new BadCredentialsException( "用户名或密码错误!" ); } } } |
其中supports方法指定使用自定义的token,additionalAuthenticationChecks方法和父类的逻辑一模一样,我只是更改了异常返回的信息。
接下来是处理认证成功和认证失败的handler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class FrontAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { public FrontAuthenticationSuccessHandler(String defaultTargetUrl) { super (defaultTargetUrl); } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { super .onAuthenticationSuccess(request, response, authentication); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class FrontAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { public FrontAuthenticationFailureHandler(String defaultFailureUrl) { super (defaultFailureUrl); } @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { super .onAuthenticationFailure(request, response, exception); } } |
最后就是最重要的security config 了:
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
|
import com.service.user.CustomerService; import com.web.filter.SiteMeshFilter; import com.web.mySecurity.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.StandardPasswordEncoder; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; import org.springframework.web.filter.CharacterEncodingFilter; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @Configuration @EnableWebSecurity public class SecurityConfig extends AbstractSecurityWebApplicationInitializer { @Bean public PasswordEncoder passwordEncoder() { return new StandardPasswordEncoder( "MD5" ); } @Autowired private CustomerService customerService; @Configuration @Order ( 1 ) public static class FrontendWebSecurityConfigureAdapter extends WebSecurityConfigurerAdapter { @Autowired private MyValidCodeProcessingFilter myValidCodeProcessingFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers( "/user/login" , "/user/logout" ).permitAll() .anyRequest().authenticated() .and() .addFilterBefore(myValidCodeProcessingFilter, UsernamePasswordAuthenticationFilter. class ) .formLogin() .loginPage( "/user/login" ) .and() .logout() .logoutUrl( "/user/logout" ) .logoutSuccessUrl( "/user/login" ); } } @Bean (name = "frontAuthenticationProvider" ) public MyAuthenticationProvider frontAuthenticationProvider() { MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider(); myAuthenticationProvider.setUserDetailsService(customerService); myAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return myAuthenticationProvider; } @Bean public AuthenticationManager authenticationManager() { List<AuthenticationProvider> list = new ArrayList<>(); list.add(frontAuthenticationProvider()); AuthenticationManager authenticationManager = new ProviderManager(list); return authenticationManager; } @Bean public MyValidCodeProcessingFilter myValidCodeProcessingFilter(AuthenticationManager authenticationManager) { MyValidCodeProcessingFilter filter = new MyValidCodeProcessingFilter(); filter.setAuthenticationManager(authenticationManager); filter.setAuthenticationSuccessHandler(frontAuthenticationSuccessHandler()); filter.setAuthenticationFailureHandler(frontAuthenticationFailureHandler()); return filter; } @Bean public FrontAuthenticationFailureHandler frontAuthenticationFailureHandler() { return new FrontAuthenticationFailureHandler( "/user/login" ); } @Bean public FrontAuthenticationSuccessHandler frontAuthenticationSuccessHandler() { return new FrontAuthenticationSuccessHandler( "/front/test" ); } @Bean public MyAuthenticationEntryPoint myAuthenticationEntryPoint() { return new MyAuthenticationEntryPoint( "/user/login" ); } } |
首先是一个加密类的bean,customerService是一个简单的查询用户
1
2
3
4
5
6
7
8
9
10
11
|
@Service ( "customerService" ) public class CustomerServiceImpl implements CustomerService { @Autowired private UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userDao.findCustomerByUsername(username); } } |
下来就是FrontendWebSecurityConfigureAdapter了,重写了configure方法,先禁用csrf, 开启授权请求authorizeRequests(),其中”/user/login”, “/user/logout”放过权限验证, 其他请求需要进行登录认证, 然后是addFilterBefore(),把我自定义的myValidCodeProcessingFilter添加到security默认的UsernamePasswordAuthenticationFilter之前,也就是先进行我的自定义参数认证, 然后是formLogin(),配置登录url以及登出url,登录登出url都需要进行controller映射,也就是要自己写controller。
接下来就是AuthenticationProvider,AuthenticationManager,ProcessingFilter,AuthenticationFailureHandler,AuthenticationSuccessHandler,EntryPoint的bean显示声明。
下面是login.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<body> <div class = "login_div" > <form:form autocomplete= "false" commandName= "userDTO" method= "post" > <div> <span class = "error_tips" ><b>${SPRING_SECURITY_LAST_EXCEPTION.message}</b></span> </div> username:<form:input path= "username" cssClass= "form-control" /><br/> password:<form:password path= "password" cssClass= "form-control" /><br/> validateCode:<form:input path= "validateCode" cssClass= "form-control" /> <label>${validate_code}</label> <div class = "checkbox" > <label> <input type= "checkbox" name= "remember-me" />记住我 </label> </div> <input type= "submit" class = "btn btn-primary" value= "submit" /> </form:form> </div> </body> |
验证码验证失败的时候抛出的是ValidCodeErrorException,由于它继承AuthenticationException,security在验证的时候遇到AuthenticationException就会触发AuthenticationFailureHandler,上面的bean中声明了认证失败跳转到登录url,所以login.jsp里面有${SPRING_SECURITY_LAST_EXCEPTION.message}获取我认证时抛出异常信息,能友好的提示用户。
整个自定义security验证流程就走完了
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://blog.csdn.net/u010425898/article/details/53690140?locationNum=4&fps=1