这里的双模意思是同时支持基于客户端证书和表单登录的认证.大概思路,如果用户提供了客户端证书,优先认证客户端证书,如果认证失败就跳转到基于表单的认证.
1.tomcat或jetty开启客户端认证配置可以看前面的文章.可能要调节客户端认证参数,如tomcat应将clientAuth设为want,
2.配置:
package com.berchina.uums.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
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;
import javax.annotation.Resource;
/**
* Created by on 16/6/16.
*/
@Configuration
public class SecurityConfig {
@Configuration
@EnableWebSecurity
@PropertySource("classpath:config.properties")
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private Environment env;
@Bean
public ReloadableResourceBundleMessageSource messageSource() {//本地化(不完全)
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:org/springframework/authentication/messages");
return messageSource;
}
@Autowired
@Qualifier("cerUserDetailsServiceImpl")
private UserDetailsService cerUserDetailsServiceImpl;
@Autowired
@Qualifier("daoUserDetailsServiceImpl")
private UserDetailsService daoUserDetailsServiceImpl;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(daoUserDetailsServiceImpl);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().x509().userDetailsService(cerUserDetailsServiceImpl)
.and().formLogin().loginPage("/login").permitAll()
.and().logout().logoutUrl("/logout").permitAll()
.and().exceptionHandling().accessDeniedPage("/exclude/403");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**", "/exclude/**");
}
//暴露AuthenticationManager注册成Bean供@EnableGlobalMethodSecurity使用
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
}
上面定义了两个UserDetailsService,就是为了将基于客户端证书和表单登录的认证加载用户分离
3.定义两个UserDetailsService
package com.berchina.uums.service;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* Created by on 16/6/16.
*/
@Service
public class CerUserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
throw new UsernameNotFoundException("没有找到这样的用户");
//return new User("client","client", AuthorityUtils.createAuthorityList("USER","ADMIN"));
}
}
package com.berchina.uums.service;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* Created by on 16/6/16.
*/
@Service
public class DaoUserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("client","client", AuthorityUtils.createAuthorityList("USER","ADMIN"));
}
}
4.测试:
a.https://localhost:8443/testweb/test(包含证书有没有效,即使有效,在数据库能不能找到对应的用户)
b.http://localhost:8080/testweb/test(不是提供证书,只能走表单登录认证)
要思考的问题:
使用证书认证的用户是不需要密码的,如果使用了提供给PreAuthenticatedAuthenticationProvider关于用户信息的一样的UserDetailsService来支持基于表单认证的话,就意味着有潜在的安全风险,因为打算只用证书认证的用户有可能会使用表单登录凭据来认证.有几种方法解决此问题:
a.确保使用证书认证的用户在你的用户存储中,使用合适强度的密码.
b.考虑自定义用户存储来区分那些用户启用了基于表单登录.这可以在用户账号信息表中添加一个字段,并稍为调整下使用JdbcDaoImpl对象的SQL查询来跟踪.
c.为证书登录认证用户配置一个独立的用户详细存储,完全分离那些允许使用基于表单登录的用户.