用户认证需要验证用户的密码是否正确,但是为了保护用户隐私,防止用户账号密码泄露,用户在注册的时候,都需要将密码进行加密。那么在用户认证的时候,就需要将原始密码和加密密码进行验证。这样就需要注册加密和验证加密的方式保存一致。
我们就补充下加密的概念,前面咱们已经在数据库密码加密时,学习了对称加密和非对称加密的概念,知道了私钥和公钥的作用。不管是对称还是非对称加密,都是可以将密码还原成真实信息。用户的密码肯定不希望被别人知道,所以咱们需要一种单向加密的算法,只能加密,无法还原加密前的信息。
一般咱们使用的MD5加密算法,这种方式如果大家都知道加密算法,也是有可能被暴力破解的。所以人们又想到在加密的时候,添加一个自定义的特殊“盐”,这样暴力破解就很难了。
所以我们用户密码加密,需要一个单向的加密算法。
在springsecurity中,提供了新老两种加密方式,但是接口名称都叫PasswordEncoder。这就让人很头大了,security又为了兼容,所以导致本来简单的添加加密实现,变得复杂了。我们就来看看这两个加密接口的区别。
老接口:
org.springframework.security.authentication.encoding.PasswordEncoder
@Deprecated
public interface PasswordEncoder {
String encodePassword(String arg0, Object arg1);
boolean isPasswordValid(String arg0, String arg1, Object arg2);
}
新接口:
org.springframework.security.crypto.password.PasswordEncoder
public interface PasswordEncoder {
String encode(CharSequence arg0);
boolean matches(CharSequence arg0, String arg1);
}
我们可以看到两个接口,就两个方法,一个方法用来加密,一个方法用来验证密码。除了方法名称变了,新接口别老接口少了一个Object 参数,这个参数就是“盐”。
具体的理论我们就不深究了,大概的意思就是,新接口已经采用了随机“盐”概念,不需要再自己指定一个盐了。
所以咱们这里就使用新接口的实现类BCryptPasswordEncoder就可以了。
接下来,咱们就开始security的加密整合吧。
用户认证流程,咱们前面并没有研究,只是知道自己实现一个UserDetailsService接口,并将对象托管给spring。剩下的就交给security自己去识别使用了。
现在需要增加认证的加密机制,本来很简单,只需要在配置类WebSecurityConfigurerAdapter中重写认证配置方法,把加密工具对象给builder就可以了。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(getUserDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
但是现在有两个都叫PasswordEncoder的加密接口,把事情搞大了。
这么处理怎么都不行,所以咱们还是得研究整个用户认证流程。
所以我们需要的工作,
第一,手动初始化DaoAuthenticationProvider对象,把PasswordEncoder和UserDetailsService注入进入。
第二,UserDetailsService中的用户密码替换为加密后的密码。
先用BCryptPasswordEncoder生成加密密码
@Test
public void test2() throws Exception{
//密码
String password = "123456";
// String password = "123";
System.out.println("加密前的密码:"+password);
//加密后的密文
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password2 = encoder.encode(password);
String password3= "$2a$10$.hZRd4YGFHVlMRRSrIg2ou7WQr2gt4oA/NPBcrjM/KTgiXiFEN4g.";//123456
// String password3= "$2a$10$ttZd.w89KMw5OiKxxDZ/LuMiEWZ8V3wk.p6mVa14xCHte0sLy1pNe";//123
System.out.println("加密后的密码:"+password2);
System.out.println("密码是否匹配:"+encoder.matches(password, password2));
System.out.println("密码是否匹配:"+encoder.matches(password, password3));
}
第一,手动初始化DaoAuthenticationProvider对象
第二,UserDetailsService中的用户密码替换为加密后的密码。
回顾总结
用户密码使用单向加密算法,security有两个PasswordEncoder,使用最新随机盐方式的BCryptPasswordEncoder。了解security的用户认证流程,认证方式很多,可以找到对应的AuthenticationProvider。
补充下源码
WebSecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 为了验证密码机密机制,
* 这里手动初始化了WebUserDetailsService、AuthenticationProvider
* 另外还需要使用重写父类方法configure(AuthenticationManagerBuilder auth)
* auth.userDetailsService(getUserDetailsService());
*/
@Bean
public WebUserDetailsService getUserDetailsService() {
return new WebUserDetailsService();
}
@Bean
public AuthenticationProvider getAuthenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(new PasswordEncoder(){
public String encodePassword(String rawPassword, Object arg1) {
return new BCryptPasswordEncoder().encode(rawPassword);
}
public boolean isPasswordValid(String encodedPassword, String rawPassword, Object arg2) {
return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);
}
});
return authenticationProvider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(getUserDetailsService());
}
/**
* 创建自定义用户授权拦截器,MySecurityInterceptor;
* 这里不能将自定义拦截器托管个spring,否则会重复拦截;
* 只能手动创建对象,并且插入到FilterSecurityInterceptor之前。
* .addFilterBefore(getMySecurityInterceptor(), FilterSecurityInterceptor.class);
*/
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
MySecurityInterceptor getMySecurityInterceptor(){
MySecurityInterceptor ms = new MySecurityInterceptor(myAccessDecisionManager);
ms.setSecurityMetadataSource(securityMetadataSource);
return ms;
}
protected void configure(HttpSecurity http) throws Exception {
http
//URL权限控制,优先级从上到下
.authorizeRequests()
.antMatchers("/easyui/**","/**/*.js","/**/*.css","/**/*.png").permitAll() //无需授权的资源
.antMatchers("/test/admin/**").hasRole("ADMIN") //指定角色授权,直接使用hasRole方法
.antMatchers("/test/web/**").access("hasRole('ADMIN') or hasRole('NORMAL')") //指定角色授权,使用access方法
.anyRequest().authenticated() //默认所有资源都需要授权
//自定义登录逻辑
.and()
.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/login") //登录请求,默认url是/login
// .successForwardUrl("successForwardUrl") //登录成功跳转地址
// .failureForwardUrl("failureForwardUrl") //登录失败跳转地址
.permitAll() //允许登录相关的请求,所有人可以访问
//自定义注销逻辑
.and()
.logout()
// .logoutUrl("/my/logout") //默认url是/logout
.logoutSuccessUrl("/") //注销成功后,跳转指定页面
.invalidateHttpSession(true)
//关闭额外安全机制
.and()
.csrf().disable()//关闭csrf防跨站请求伪造机制,不然ajax访问无法使用
.headers().frameOptions().disable();//关闭iframe防攻击机制,不然iframe无法调用URL
//添加自定义的拦截器到security拦截链中
http
//自定义用户授权拦截器,插入到FilterSecurityInterceptor之前。
.addFilterBefore(getMySecurityInterceptor(), FilterSecurityInterceptor.class);
}
}
WebUserDetailsService
public class WebUserDetailsService implements UserDetailsService {
private final static Logger logger = LoggerFactory.getLogger(WebUserDetailsService.class);
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
logger.info("用户登录:"+username);
if (StringUtils.isEmpty(username)) {
return null;
}else if("admin".equals(username) ){
// UserDetails user = User.withUsername(username).password("123456").roles("ADMIN","NORMAL").build();
//这里使用自定义权限列表的方式初始化权限
List grantedAuthorities = new ArrayList ();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
grantedAuthorities.add(new SimpleGrantedAuthority("delete"));
// UserDetails user = new User(username,"123456",grantedAuthorities);
UserDetails user = new User(username,"$2a$10$.hZRd4YGFHVlMRRSrIg2ou7WQr2gt4oA/NPBcrjM/KTgiXiFEN4g.",grantedAuthorities);
return user;
}else{
// UserDetails user = User.withUsername(username).password("123").roles("NORMAL").build();
//这里使用自定义权限列表的方式初始化权限
List grantedAuthorities = new ArrayList ();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_NORMAL"));
// UserDetails user = new User(username,"123",grantedAuthorities);
UserDetails user = new User(username,"$2a$10$ttZd.w89KMw5OiKxxDZ/LuMiEWZ8V3wk.p6mVa14xCHte0sLy1pNe",grantedAuthorities);
return user;
}
}
}