配置中之前我们写了关于密码模块是这样的:
@Bean
PasswordEncoder PasswordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
这里配置不使用密码Encode策略,但是存储的密码要是明文显示的就非常危险,数据库泄露就什么都没了,我们这里需要配置下策略
1:MD5加密,这种加密方式随便搜索一下到处有(缺点:黑客搞出了一个交彩虹表的东西,所以现在MD5已经变成了伪不可加密)
2:Bcrypt加密,这种加密方式每次计算出来的值都不一样,所以安全性高(缺点个人认为可以忽略)
使用:
测试1:
String pwd=new BCryptPasswordEncoder().encode("admin123");
System.out.println(pwd);
反复运行这段代码,发现得到的值都是不一样的,非常棒
我们得到的值为:
$2a$10$Mlh4urQFaC5peoPRZOa6ruLM1qHrt5/93Wz2IYNtAExj7OanYiuty
$2a$10$j9E4pPQUJ6eL1h.56/MOw.nvtsefar/FxjZU2l.sjClbEYjUDM/Ye
$2a$10$jLwDEkHrFiWYDeRFy3DcHOoYJ3jJtlAtANF7f.A2abYZBLFr6AQtu
调用提供的方法
得到的结果是我们所预期的结果
我们现在把这种密码策略应用到我们的spring security中去
@Bean
PasswordEncoder PasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider authenticationProvider =new DaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(PasswordEncoder());
auth.inMemoryAuthentication().withUser("admin").password("$2a$10$jLwDEkHrFiWYDeRFy3DcHOoYJ3jJtlAtANF7f.A2abYZBLFr6AQtu").roles("admin")
.and()
.withUser("zhangsan").password("123").roles("user");
}
这里因为我们是使用自己设置的账户密码,所以把转码过后的密码填写进去
还有注意的是
@EnableGlobalMethodSecurity(prePostEnabled=true
这个必须启用这个配置,实验完成
但是我们平时的用户数据记录是放在数据库中的,而不是再我们的配置文件中直接把用户名和密码配置的,就像我们之前配置的这样
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider authenticationProvider =new DaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(PasswordEncoder());
auth.inMemoryAuthentication().withUser("admin").password("$2a$10$jLwDEkHrFiWYDeRFy3DcHOoYJ3jJtlAtANF7f.A2abYZBLFr6AQtu").roles("admin")
.and()
.withUser("zhangsan").password("123").roles("user");
}
这种模式仅仅做测试用,我们生产环境下还是要依据数据中中的数据来保存用户信息
所以需要改版:
1:在项目中使用mybatis来完成从数据库读取数据
2:配置security完成从数据库加载数据的校验(代码连接在文章末尾)
这里我们需要关心的第一个类是UserDetails
这个类,源代码:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
这个类包含某个用户的账号,密码,权限,状态(是否锁定)等信息。
实验目的:暂时使用spring security自带的登录页面完成spring security登录操作
注意点:因为现在项目都是前后端分离项目,所以我们将登录成功与否的功能交由json返回给前端
完成UserDetailService的编写
public class UserDetailService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
User u=userService.findUserByName(name);
if (u==null){
throw new UsernameNotFoundException(null);
}
org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(u.getUsername(), u.getPassword(), true, true, true, true,getGrantedAuthority(u));
return user;
}
public List<GrantedAuthority> getGrantedAuthority(User user){
List<GrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return list;
}
}
这里loadUserByUsername就是用于加载用户,如果u==null 代表查询部出来任何对象,就返回UsernameNotFoundException ,如果能查询出来对象,就组建userdetails.User对象,由该对象判断是否登录成功
编写类:CustomAuthenticationSuccessHandler
该类用户登录成功的处理函数
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> data = new HashMap<>();
String responseJSONObject ="";
data.put("code", ResultCode.SUCCESS.val());
data.put("message",ResultCode.SUCCESS);
responseJSONObject = JSONObject.toJSONString(data);
PrintWriter out = response.getWriter();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
out.print(responseJSONObject);
}
}
这里返回的就是json
编写类CustomAuthenticationFailureHandler
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Map<String, Object> data = new HashMap<>();
String responseJSONObject ="";
if(e instanceof UsernameNotFoundException || e instanceof BadCredentialsException){
data.put("code",ResultCode.LOGIN_FAIL.val());
data.put("message",ResultCode.LOGIN_FAIL);
responseJSONObject = JSONObject.toJSONString(data);
}
PrintWriter out = response.getWriter();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
out.print(responseJSONObject);
}
}
这里通过返回的异常判断是为什么失败的
UsernameNotFoundException -代表没有找到这个对象
BadCredentialsException-错误的账户名/密码
最后完成配置,去掉之前我们手动添加的账户名和密码
@Bean
AuthenticationFailureHandler customAuthenticationFailureHandler(){
return new CustomAuthenticationFailureHandler();
}
@Bean
CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler(){
return new CustomAuthenticationSuccessHandler();
}
@Bean
UserDetailsService customUserService(){
return new UserDetailService();
}
@Bean
PasswordEncoder PasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()).passwordEncoder(PasswordEncoder());
}
这里AuthenticationManagerBuilder 就不是自己写的而是通过customUserService()获取
最后配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/sayHello").hasRole("admin")
.anyRequest().authenticated()
.and().formLogin().permitAll()
.successHandler(customAuthenticationSuccessHandler())
.failureHandler(customAuthenticationFailureHandler())
.and().csrf().disable();
}
这样,successHandler和failureHandler就换成我们自己写的上述类
最后运行代码,当登录成功/失败的时间返回json格式字符串