学习SpringSecurity的日常生活(三)——前后台分离登录


前言

上次学习了自定义登录页面实现了前后端表单不分离的登陆操作,这次我再记录一下学习前后端分离的登陆操作

步骤

一.重写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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值