基于java config的springSecurity(二)--自定义认证

来源:互联网 发布:什么是js面向对象编程 编辑:程序博客网 时间:2024/06/01 19:06

可参考的资料:

http://blog.csdn.net/xiejx618/article/details/42523337

http://blog.csdn.net/xiejx618/article/details/22902343


本文在前文的基础上进行修改.
一.根据传过来的用户名和密码实现自定义的认证逻辑.将基于内存的AuthenticationProvider改为自定义的AuthenticationProvider,实现认证(Authentication),还没有实现授权(Authorization)
1.修改实体User类实现org.springframework.security.core.userdetails.UserDetails作为spring security管理的用户.修改实体Role类实现org.springframework.security.core.GrantedAuthority作为spring security管理的简单授权权限

2.先自定义UserDetailsService,以供AuthenticationProvider使用.使用构造方法注入UserRepository,调用org.exam.repository.UserRepositoryCustom#findByUsernameWithAuthorities,根据用户名来返回spring security管理的用户.因为org.exam.domain.User#authorities是懒加载的,可以参考http://blog.csdn.net/xiejx618/article/details/21794337来解决懒加载问题.

[java] view plain copy
  1. package org.exam.auth;  
  2. import org.exam.repository.UserRepository;  
  3. import org.springframework.security.core.userdetails.UserDetails;  
  4. import org.springframework.security.core.userdetails.UserDetailsService;  
  5. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  6. /** 
  7.  * Created by xin on 15/1/10. 
  8.  */  
  9. public class UserDetailsServiceCustom implements UserDetailsService {  
  10.     private final UserRepository userRepository;  
  11.     public UserDetailsServiceCustom(UserRepository userRepository) {  
  12.         this.userRepository = userRepository;  
  13.     }  
  14.     @Override  
  15.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
  16.         return userRepository.findByUsernameWithAuthorities(username);  
  17.     }  
  18. }  
3.再自定义AuthenticationProvider.
[java] view plain copy
  1. package org.exam.auth;  
  2. import org.springframework.security.authentication.*;  
  3. import org.springframework.security.core.Authentication;  
  4. import org.springframework.security.core.AuthenticationException;  
  5. import org.springframework.security.core.userdetails.UserDetails;  
  6. import org.springframework.security.core.userdetails.UserDetailsService;  
  7. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  8. /** 
  9.  * Created by xin on 15/1/10. 
  10.  */  
  11. public class AuthenticationProviderCustom implements AuthenticationProvider {  
  12.     private final UserDetailsService userDetailsService;  
  13.     public AuthenticationProviderCustom(UserDetailsService userDetailsService) {  
  14.         this.userDetailsService = userDetailsService;  
  15.     }  
  16.     @Override  
  17.     public Authentication authenticate(Authentication authentication) throws AuthenticationException {  
  18.         UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;  
  19.         String username = token.getName();  
  20.         //从数据库找到的用户  
  21.         UserDetails userDetails = null;  
  22.         if(username != null) {  
  23.             userDetails = userDetailsService.loadUserByUsername(username);  
  24.         }  
  25.         //  
  26.         if(userDetails == null) {  
  27.             throw new UsernameNotFoundException("用户名/密码无效");  
  28.         }else if (!userDetails.isEnabled()){  
  29.             throw new DisabledException("用户已被禁用");  
  30.         }else if (!userDetails.isAccountNonExpired()) {  
  31.             throw new AccountExpiredException("账号已过期");  
  32.         }else if (!userDetails.isAccountNonLocked()) {  
  33.             throw new LockedException("账号已被锁定");  
  34.         }else if (!userDetails.isCredentialsNonExpired()) {  
  35.             throw new LockedException("凭证已过期");  
  36.         }  
  37.         //数据库用户的密码  
  38.         String password = userDetails.getPassword();  
  39.         //与authentication里面的credentials相比较  
  40.         if(!password.equals(token.getCredentials())) {  
  41.             throw new BadCredentialsException("Invalid username/password");  
  42.         }  
  43.         //授权  
  44.         return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());  
  45.     }  
  46.   
  47.     @Override  
  48.     public boolean supports(Class<?> authentication) {  
  49.         //返回true后才会执行上面的authenticate方法,这步能确保authentication能正确转换类型  
  50.         return UsernamePasswordAuthenticationToken.class.equals(authentication);  
  51.     }  
  52. }  

4.配置自定义的AuthenticationProvider

[java] view plain copy
  1. package org.exam.config;  
  2. import org.exam.auth.AuthenticationProviderCustom;  
  3. import org.exam.auth.UserDetailsServiceCustom;  
  4. import org.exam.repository.UserRepository;  
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.context.annotation.Bean;  
  7. import org.springframework.context.annotation.Configuration;  
  8. import org.springframework.security.authentication.AuthenticationProvider;  
  9. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  
  10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
  11. import org.springframework.security.config.annotation.web.builders.WebSecurity;  
  12. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;  
  13. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
  14. import org.springframework.security.core.userdetails.UserDetailsService;  
  15. /** 
  16.  * Created by xin on 15/1/7. 
  17.  */  
  18. @Configuration  
  19. @EnableWebSecurity  
  20. public class SecurityConfig extends WebSecurityConfigurerAdapter {  
  21.     @Autowired  
  22.     private UserRepository userRepository;  
  23.     @Bean  
  24.     public UserDetailsService userDetailsService(){  
  25.         UserDetailsService userDetailsService=new UserDetailsServiceCustom(userRepository);  
  26.         return userDetailsService;  
  27.     }  
  28.     @Bean  
  29.     public AuthenticationProvider authenticationProvider(){  
  30.         AuthenticationProvider authenticationProvider=new AuthenticationProviderCustom(userDetailsService());  
  31.         return authenticationProvider;  
  32.     }  
  33.     @Override  
  34.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
  35.         //暂时使用基于内存的AuthenticationProvider  
  36.         //auth.inMemoryAuthentication().withUser("username").password("password").roles("USER");  
  37.         //自定义AuthenticationProvider  
  38.         auth.authenticationProvider(authenticationProvider());  
  39.     }  
  40.     @Override  
  41.     public void configure(WebSecurity web) throws Exception {  
  42.         web.ignoring().antMatchers("/static/**");  
  43.     }  
  44.     @Override  
  45.     protected void configure(HttpSecurity http) throws Exception {  
  46.         //暂时禁用csrf,并自定义登录页和登出URL  
  47.         http.csrf().disable()  
  48.                 .authorizeRequests().anyRequest().authenticated()  
  49.                 .and().formLogin().loginPage("/login").failureUrl("/login?error").usernameParameter("username").passwordParameter("password").permitAll()  
  50.                 .and().logout().logoutUrl("/logout").permitAll();  
  51.     }  
  52. }  
5.在页面上可以使用如下的代码获得抛出的异常信息.
[html] view plain copy
  1. <c:if test="${SPRING_SECURITY_LAST_EXCEPTION.message != null}">  
  2.        <p>  
  3.           ${SPRING_SECURITY_LAST_EXCEPTION.message}  
  4.        </p>  
  5.    </c:if>  

源码:http://download.csdn.net/detail/xiejx618/8349649

二.加入验证码功能.看过Spring Security 3.x Cookbook的Spring Security with Captcha integration,觉得验证码附加到用户名这种方式非常丑陋,其实验证码验证逻辑也不应在UserDetailsService.loadUserByUsername方法,因为这个方法不止在输入用户码密码登录时调用,比如记住我自动登录功能,也会调用此方法.基于xml的方式,可以使用<custom-filter position="FORM_LOGIN_FILTER" ref="multipleInputAuthenticationFilter" />来替换默认的UsernamePasswordAuthenticationFilter,但基于javaConfig的方式似乎没有等效的配置,所以替换默认的UsernamePasswordAuthenticationFilter的路不通.因为验证验证码逻辑比用户名密码的逻辑要先,我的思路在UsernamePasswordAuthenticationFilter之前再添加一个KaptchaAuthenticationFilter.1.修改配置org.exam.config.WebSecurityConfig#configure
[java] view plain copy
  1. @Override  
  2. protected void configure(HttpSecurity http) throws Exception {  
  3.     http.addFilterBefore(new KaptchaAuthenticationFilter("/login""/login?error"), UsernamePasswordAuthenticationFilter.class)  
  4.             .csrf().disable()  
  5.             .authorizeRequests().anyRequest().authenticated()  
  6.             .and().formLogin().loginPage("/login").failureUrl("/login?error").usernameParameter("username").passwordParameter("password").permitAll()  
  7.             .and().logout().logoutUrl("/logout").permitAll();  
  8.   
  9. }  

HttpSecurity有addFilterBefore,addFilterAfter,就没有replaceFilter,不然第一行配置都省了,所以思路只能这么来.先看看KaptchaAuthenticationFilter,

注:http://docs.spring.io/spring-security/site/docs/4.1.0.RC2/reference/htmlsingle/#new开始提供HttpSecurity.addFilterAt

2.继承AbstractAuthenticationProcessingFilter或者UsernamePasswordAuthenticationFilter,是为了利用验证失败时的处理(跳到failureUrl显示异常信息)
[java] view plain copy
  1. package org.exam.config;  
  2. import com.google.code.kaptcha.Constants;  
  3. import org.springframework.security.authentication.InsufficientAuthenticationException;  
  4. import org.springframework.security.core.Authentication;  
  5. import org.springframework.security.core.AuthenticationException;  
  6. import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;  
  7. import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;  
  8. import javax.servlet.*;  
  9. import javax.servlet.http.HttpServletRequest;  
  10. import javax.servlet.http.HttpServletResponse;  
  11. import java.io.IOException;  
  12. /** 
  13.  * Created by xin on 15/1/7. 
  14.  */  
  15.   
  16. public class KaptchaAuthenticationFilter extends AbstractAuthenticationProcessingFilter {  
  17.     private String servletPath;  
  18.     public KaptchaAuthenticationFilter(String servletPath,String failureUrl) {  
  19.         super(servletPath);  
  20.         this.servletPath=servletPath;  
  21.         setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));  
  22.   
  23.     }  
  24.   
  25.     @Override  
  26.   
  27.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
  28.         HttpServletRequest req = (HttpServletRequest) request;  
  29.         HttpServletResponse res=(HttpServletResponse)response;  
  30.         if ("POST".equalsIgnoreCase(req.getMethod())&&servletPath.equals(req.getServletPath())){  
  31.             String expect = (String) req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);  
  32.             if(expect!=null&&!expect.equalsIgnoreCase(req.getParameter("kaptcha"))){  
  33.                 unsuccessfulAuthentication(req, res, new InsufficientAuthenticationException("输入的验证码不正确"));  
  34.                 return;  
  35.             }  
  36.         }  
  37.         chain.doFilter(request,response);  
  38.     }  
  39.   
  40.     @Override  
  41.     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {  
  42.         return null;  
  43.   
  44.     }  
  45.   
  46. }  
attemptAuthentication回调不用理,重写了doFilter方法,它不会被调用,从以上代码可知使用了google的kaptcha生成验证码,下面看看如何配置.
3.kaptcha的依赖如下
[html] view plain copy
  1. <dependency>  
  2.     <groupId>com.github.penggle</groupId>  
  3.     <artifactId>kaptcha</artifactId>  
  4.     <version>2.3.2</version>  
  5. </dependency>  
然后在DispatcherServletInitializer添加一个kaptcha servlet,
org.exam.config.DispatcherServletInitializer#onStartup
[java] view plain copy
  1. @Override  
  2. public void onStartup(ServletContext servletContext) throws ServletException {  
  3.     super.onStartup(servletContext);  
  4.     FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encoding-filter", CharacterEncodingFilter.class);  
  5.     encodingFilter.setInitParameter("encoding""UTF-8");  
  6.     encodingFilter.setInitParameter("forceEncoding""true");  
  7.     encodingFilter.setAsyncSupported(true);  
  8.     encodingFilter.addMappingForUrlPatterns(nullfalse"/*");  
  9.     ServletRegistration.Dynamic kaptchaServlet = servletContext.addServlet("kaptcha-servlet", KaptchaServlet.class);  
  10.     kaptchaServlet.addMapping("/except/kaptcha");  
  11. }  
4.不要忘了/except/kaptcha的请求被拦截了,所以要忽略掉
[java] view plain copy
  1. @Override  
  2. public void configure(WebSecurity web) throws Exception {  
  3.     web.ignoring().antMatchers("/static/**""/except/**");  
  4. }  
页面加入验证输入域,然后测试
[html] view plain copy
  1. <input type="text" id="kaptcha" name="kaptcha"/><img src="/testweb/except/kaptcha" width="80" height="25"/>  


阅读全文
0 0
原创粉丝点击