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

可参考的资料:

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来解决懒加载问题.

package org.exam.auth;
import org.exam.repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
 * Created by xin on 15/1/10.
 */
public class UserDetailsServiceCustom implements UserDetailsService {
	private final UserRepository userRepository;
	public UserDetailsServiceCustom(UserRepository userRepository) {
		this.userRepository = userRepository;
	}
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		return userRepository.findByUsernameWithAuthorities(username);
	}
}
3.再自定义AuthenticationProvider.
package org.exam.auth;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
 * Created by xin on 15/1/10.
 */
public class AuthenticationProviderCustom implements AuthenticationProvider {
	private final UserDetailsService userDetailsService;
	public AuthenticationProviderCustom(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
		String username = token.getName();
		//从数据库找到的用户
		UserDetails userDetails = null;
		if(username != null) {
			userDetails = userDetailsService.loadUserByUsername(username);
		}
		//
		if(userDetails == null) {
			throw new UsernameNotFoundException("用户名/密码无效");
		}else if (!userDetails.isEnabled()){
			throw new DisabledException("用户已被禁用");
		}else if (!userDetails.isAccountNonExpired()) {
			throw new AccountExpiredException("账号已过期");
		}else if (!userDetails.isAccountNonLocked()) {
			throw new LockedException("账号已被锁定");
		}else if (!userDetails.isCredentialsNonExpired()) {
			throw new LockedException("凭证已过期");
		}
		//数据库用户的密码
		String password = userDetails.getPassword();
		//与authentication里面的credentials相比较
		if(!password.equals(token.getCredentials())) {
			throw new BadCredentialsException("Invalid username/password");
		}
		//授权
		return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());
	}

	@Override
	public boolean supports(Class<?> authentication) {
		//返回true后才会执行上面的authenticate方法,这步能确保authentication能正确转换类型
		return UsernamePasswordAuthenticationToken.class.equals(authentication);
	}
}

4.配置自定义的AuthenticationProvider

package org.exam.config;
import org.exam.auth.AuthenticationProviderCustom;
import org.exam.auth.UserDetailsServiceCustom;
import org.exam.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
 * Created by xin on 15/1/7.
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private UserRepository userRepository;
	@Bean
	public UserDetailsService userDetailsService(){
		UserDetailsService userDetailsService=new UserDetailsServiceCustom(userRepository);
		return userDetailsService;
	}
	@Bean
	public AuthenticationProvider authenticationProvider(){
		AuthenticationProvider authenticationProvider=new AuthenticationProviderCustom(userDetailsService());
		return authenticationProvider;
	}
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//暂时使用基于内存的AuthenticationProvider
		//auth.inMemoryAuthentication().withUser("username").password("password").roles("USER");
		//自定义AuthenticationProvider
		auth.authenticationProvider(authenticationProvider());
	}
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/static/**");
	}
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//暂时禁用csrf,并自定义登录页和登出URL
		http.csrf().disable()
				.authorizeRequests().anyRequest().authenticated()
				.and().formLogin().loginPage("/login").failureUrl("/login?error").usernameParameter("username").passwordParameter("password").permitAll()
				.and().logout().logoutUrl("/logout").permitAll();
	}
}
5.在页面上可以使用如下的代码获得抛出的异常信息.

 <c:if test="${SPRING_SECURITY_LAST_EXCEPTION.message != null}">
        <p>
           ${SPRING_SECURITY_LAST_EXCEPTION.message}
        </p>
    </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
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.addFilterBefore(new KaptchaAuthenticationFilter("/login", "/login?error"), UsernamePasswordAuthenticationFilter.class)
            .csrf().disable()
            .authorizeRequests().anyRequest().authenticated()
            .and().formLogin().loginPage("/login").failureUrl("/login?error").usernameParameter("username").passwordParameter("password").permitAll()
            .and().logout().logoutUrl("/logout").permitAll();

}

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显示异常信息)
package org.exam.config;
import com.google.code.kaptcha.Constants;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * Created by xin on 15/1/7.
 */

public class KaptchaAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private String servletPath;
    public KaptchaAuthenticationFilter(String servletPath,String failureUrl) {
        super(servletPath);
        this.servletPath=servletPath;
        setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));

    }

    @Override

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res=(HttpServletResponse)response;
        if ("POST".equalsIgnoreCase(req.getMethod())&&servletPath.equals(req.getServletPath())){
            String expect = (String) req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
            if(expect!=null&&!expect.equalsIgnoreCase(req.getParameter("kaptcha"))){
                unsuccessfulAuthentication(req, res, new InsufficientAuthenticationException("输入的验证码不正确"));
                return;
            }
        }
        chain.doFilter(request,response);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        return null;

    }

}
attemptAuthentication回调不用理,重写了doFilter方法,它不会被调用,从以上代码可知使用了google的kaptcha生成验证码,下面看看如何配置.
3.kaptcha的依赖如下
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
然后在DispatcherServletInitializer添加一个kaptcha servlet,
org.exam.config.DispatcherServletInitializer#onStartup
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);
    FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encoding-filter", CharacterEncodingFilter.class);
    encodingFilter.setInitParameter("encoding", "UTF-8");
    encodingFilter.setInitParameter("forceEncoding", "true");
    encodingFilter.setAsyncSupported(true);
    encodingFilter.addMappingForUrlPatterns(null, false, "/*");
    ServletRegistration.Dynamic kaptchaServlet = servletContext.addServlet("kaptcha-servlet", KaptchaServlet.class);
    kaptchaServlet.addMapping("/except/kaptcha");
}
4.不要忘了/except/kaptcha的请求被拦截了,所以要忽略掉
@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/static/**", "/except/**");
}
页面加入验证输入域,然后测试
<input type="text" id="kaptcha" name="kaptcha"/><img src="/testweb/except/kaptcha" width="80" height="25"/>



发布了157 篇原创文章 · 获赞 31 · 访问量 140万+
展开阅读全文

Spring Security 自定义权限验证方法没有被调用

04-16

我自己定义的AccessDecisionManager 和FilterInvocationSecurityMetadataSource 都没有被调用。代码如下: package org.bzxly.yx.security.filter; import org.bzxly.yx.security.entity.Authority; import org.bzxly.yx.security.service.PermissionService; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.AntPathRequestMatcher; import org.springframework.stereotype.Component; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import com.alibaba.fastjson.JSON; @Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{ private static final Log LOG = LogFactory.getLog(FilterInvocationSecurityMetadataSource.class); @Autowired private PermissionService permissionService; /** * 权限容器 * key:URL * value:角色 */ private static final Map<String,Collection<ConfigAttribute>> AUTHORITY = new HashMap<String,Collection<ConfigAttribute>>();//存储所有角色的权限 /**这个方法在访问受限资源的时候没有被调用*/ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String accessURL = ((FilterInvocation)object).getRequestUrl(); LOG.debug("访问地址:"+accessURL); FilterInvocation fi = (FilterInvocation)object; HttpServletRequest request = fi.getRequest(); Collection<ConfigAttribute> config = null; for(Entry<String, Collection<ConfigAttribute>> entry:AUTHORITY.entrySet()){ String key = entry.getKey(); AntPathRequestMatcher matcher =new AntPathRequestMatcher(key); boolean b = matcher.matches(request); if(b){ config = new ArrayList<ConfigAttribute>(); Collection<ConfigAttribute> value = entry.getValue(); for (ConfigAttribute configAttribute : value) { config.add(configAttribute); } } } return config; } //这个初始化已经在启动的时候初始化成功 @Override public Collection<ConfigAttribute> getAllConfigAttributes() { LOG.debug("正在初始化资源中"); List<Authority> authorities = permissionService.loadResource(); for(int i=0;i<authorities.size();i++){ final Authority authority=authorities.get(i); String serverURL = authority.getServerURL(); LOG.info("初始化角色:"+authority.getRoleKey()+",资源:"+serverURL); if(AUTHORITY.containsKey(serverURL)){ AUTHORITY.get(serverURL).add(new SecurityConfig(authority.getRoleKey())); }else{ AUTHORITY.put(authority.getServerURL(),new ArrayList<ConfigAttribute>(){ { add(new SecurityConfig(authority.getRoleKey())); } }); } } LOG.info("ALL LIMIT IS "+JSON.toJSONString(AUTHORITY)); LOG.debug("初始化资源完成"); return null; } @Override public boolean supports(Class<?> clazz) { return true; } } package org.bzxly.yx.security.filter; import java.util.Collection; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; @Component public class CustomAccessDecisionManager implements AccessDecisionManager{ /**这个方法在访问受限资源的时候没有被调用*/ @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(authentication ==null){ throw new InsufficientAuthenticationException("用户信息不足、、、"); } Collection<? extends GrantedAuthority> ownedRoles = authentication.getAuthorities(); System.out.println("in method decide ....."+object.getClass()); for (GrantedAuthority ownedGa : ownedRoles) { String strOwnedRole = ownedGa.getAuthority(); for(ConfigAttribute requiredCa :configAttributes){ String strRequiedRole = requiredCa.getAttribute(); if(strOwnedRole.equals(strRequiedRole)){ return; } } } throw new AccessDeniedException("您没有操作权限!!!"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } } <?xml version="1.0" encoding="UTF-8"?> <security:http pattern="/index.jsp" security="none" /> <security:http pattern="/bzxly/**" security="none" /> <security:http auto-config="false" entry-point-ref="loginUrlEntryPoint"> <security:custom-filter ref="customFilter" before="FORM_LOGIN_FILTER" /> <security:custom-filter ref="formLoginFilter" position="FORM_LOGIN_FILTER" /> <security:custom-filter ref="logoutFilter" position="LOGOUT_FILTER" /> <security:custom-filter ref="rememberMeFilter" after="FORM_LOGIN_FILTER" /> <security:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER" /> <security:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" /> <security:custom-filter ref="exceptionTranslationFilter" after="EXCEPTION_TRANSLATION_FILTER" /> </security:http> <bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="loginUrlEntryPoint"/> <property name="accessDeniedHandler" ref="accessDeniedHandler"/> </bean> <bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl"> <property name="errorPage" value="/bzxly/accessDenied" /> </bean> <bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="customAccessDecisionManager"/> <property name="securityMetadataSource" ref="customFilterInvocationSecurityMetadataSource"/> </bean> <bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"> <property name="sessionRegistry" ref="sessionRegistry" /> <property name="expiredUrl" value="/bzxly/sessionExpired" /> </bean> <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" /> <bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy"> <constructor-arg name="sessionRegistry" ref="sessionRegistry" /> <property name="maximumSessions" value="1" /> </bean> <bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter"> <property name="rememberMeServices" ref="rememberMeServices"/> <property name="authenticationManager" ref="authenticationManager" /> </bean> <bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> <property name="userDetailsService" ref="userServiceImpl"/> <property name="cookieName" value="myck"></property> <property name="tokenValiditySeconds" value="3600"></property> <property name="parameter" value="rememberMe"></property> <property name="key" value="springRocks"/> </bean> <bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider"> <property name="key" value="springRocks"/> </bean> <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> <property name="filterProcessesUrl" value="/user/logout"/> <constructor-arg index="0" value="/bzxly/login"/> <constructor-arg index="1"> <list> <ref bean="sessionInvalidateHandler"/> <ref bean="rememberMeServices"/> </list> </constructor-arg> </bean> <bean id="sessionInvalidateHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> <bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="usernameParameter" value="usname"/> <property name="passwordParameter" value="psword"/> <property name="filterProcessesUrl" value="/user/userLogin"/> <property name="authenticationSuccessHandler" ref="authenSuccessHandler"/> <property name="authenticationFailureHandler" ref="authenFailerHandler"/> <property name="rememberMeServices" ref="rememberMeServices"/> <property name="sessionAuthenticationStrategy" ref="sas"/> </bean> <bean id="authenSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <property name="alwaysUseDefaultTargetUrl" value="false"></property> <property name="defaultTargetUrl" value="/index"></property> </bean> <bean id="authenFailerHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <property name="defaultFailureUrl" value="/bzxly/loginError"/> </bean> <bean id="loginUrlEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/bzxly/login"></property> </bean> <security:authentication-manager alias="authenticationManager" erase-credentials="false"> <security:authentication-provider user-service-ref="userServiceImpl"> <security:password-encoder ref="passwordEndecrypt"> <security:salt-source ref="customSaltSource" /> </security:password-encoder> </security:authentication-provider> <security:authentication-provider ref="rememberMeAuthenticationProvider"/> </security:authentication-manager> </beans> 以上是关键代码 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览