前言
上次学习了自定义登录页面实现了前后端表单不分离的登陆操作,这次我再记录一下学习前后端分离的登陆操作
步骤
一.重写UsernamePasswordAuthenticationFilter
因为SpringSecurity里面表单登录就是用UsernamePasswordAuthenticationFilter来验证的,所以如果我们要实现前后端分离表单登陆的话我们就需要改写这个过滤器
UsernamePasswordAuthenticationFilter的原生验证代码如下:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
我们这里需要对这个过滤器的attemptAuthentication方法进行重写。
主要就是用户信息的获取从开始的表单获取改成json数据获取。
改写后的代码:
public class SeparateLoginFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//判断是否是post请求
if(!request.getMethod().equals("POST")){
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
//判断是否是json参数,提取json参数里面的用户信息
if(request.getContentType().equals("application/json")){
try {
//参数转换为map
Map map = new ObjectMapper().readValue(request.getInputStream(),Map.class);
String username = (String) map.get("username");
String password = (String) map.get("password");
System.out.println("前后端分离--》用户名:"+username+" 密码:"+password);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (IOException e) {
e.printStackTrace();
}
}
return super.attemptAuthentication(request, response);
}
}
二.配置类
1、配置过滤器
过滤器我们写好了,但是我们要把这个过滤器在配置类里面用起来,替代UsernamePasswordAuthenticationFilter
首先将自定义的过滤器在工厂里声明一下,不然spring搜不到我们这个过滤器,在Security里面加上以下代码
@Bean
public SeparateLoginFilter loginFilter() throws Exception {
SeparateLoginFilter loginFilter = new SeparateLoginFilter();
loginFilter.setFilterProcessesUrl("/selogin");
loginFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","登陆成功");
map.put("authentication",authentication);
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(string);
}));
loginFilter.setAuthenticationFailureHandler(((request, response, exception) -> {
Map<String,Object> map = new HashMap<>();
map.put("code",302);
map.put("msg","登陆失败");
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(string);
}));
loginFilter.setAuthenticationManager(authenticationManagerBean());
return loginFilter;
}
loginFilter.setFilterProcessesUrl(“/selogin”) 是前端登陆所用的路径
setAuthenticationSuccessHandler是登录成功的结果处理
setAuthenticationFailureHandler 是登陆失败的结果处理
setAuthenticationManager是设置AuthenticationManager,这里我们用自定义的AuthenticationManager,一般来说都是用自定义的,因为默认的AuthenticationManager功能比较单一
2、自定义AuthenticationManager
代码如下:
@Bean
public UserDetailsService userDetailService(){
InMemoryUserDetailsManager memoryUserDetailsManager = new InMemoryUserDetailsManager();
memoryUserDetailsManager.createUser(User.withUsername("aaa").password("{noop}123").roles("admin").build());
return memoryUserDetailsManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
configure方法覆盖了原有的AuthenticationManager ,实现自定义AuthenticationManager
但是我们自定义的AuthenticationManager目前属于工厂内部的类,所以我们得用 authenticationManagerBean方法把它暴露出来。
userDetailService是自定义数据源,我们这里暂时用内存,{noop}标识明文,不加密
其他代码
基础配置
代码如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().addFilterAt(loginFilter(),UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(((request, response, authException) -> {
Map<String,Object> map = new HashMap<>();
map.put("code",401);
map.put("msg","请先登录认证");
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(string);
}))
.and()
.logout()
.logoutSuccessHandler(((request, response, authentication) -> {
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","注销成功");
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(string);
}))
.and()
.csrf().disable();
}
测试
接下来我们就可以写个接口进行测试了
@RestController
public class LoginController {
@RequestMapping("/hello")
public String hello(){
return "hello world";
}
}
localhost:8080/hello 资源接口
localhost:8080/selogin 登录接口
localhost:8080/logout 注销接口
开始不登录直接访问资源
可以发现提示需要登录
我们跑一下登录方法
如果不是post请求或者用户名密码错误,则报错误
否则,成功
我们再来访问资源接口
显示成功了
我们接下来再执行一下注销接口
注销完再来访问资源
哎呀,又没了
完整项目代码地址:https://pan.baidu.com/s/1lFlhUz8X1yu3qMMuSvRsgw
提取码:xxgz
总结
嘿嘿,可以看到我们这里还是成功的嘛。这一次我们用的是内存数据源,下一次我们使用具体的数据库,毕竟内存只是用来测试的,实际开发还得靠数据库。
我的个人博客地址:http://www.dbhx.vip