Spring Security04--前后端分离--JSON传参

上一篇:https://blog.csdn.net/fengxianaa/article/details/124697362

1. 前后端分离

之前功能都是在前后端不分离的情况下,也就是前端的代码在我们后端的项目中

但实际工作中,大多数都是前后端分离的,这样的开发架构下,前后端的交互都是通过 JSON 来进行交互

准备,pom.xml中添加

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.60</version>
</dependency>

1. 登陆成功、失败,返回json

修改 SecurityConfig

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/fail.html").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginProcessingUrl("/user/login")
        .usernameParameter("name")
        .passwordParameter("pass")
        // 登录成功后设置json格式的返回结果
        .successHandler(new AuthenticationSuccessHandler() {
            /**
                     *
                     * @param request
                     * @param response
                     * @param authentication 保存了登录成功的用户信息
                     * @throws IOException
                     */
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                Authentication authentication) throws IOException {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.write(JSONObject.toJSONString(authentication));
                out.flush();
                out.close();
            }
        })
        // 登录失败后设置json格式的返回结果
        .failureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException exception) throws IOException, ServletException {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                Map<String,String> map = new HashMap<>();
                map.put("errMsg", exception.getMessage());
                out.write(JSONObject.toJSONString(map));
                out.flush();
                out.close();
            }
        })
        .permitAll()
        .and()
        .csrf().disable();//关闭 csrf,后面讨论
    }

用 postman 访问

登陆失败:

2. 未登陆返回json

目前用户没有登陆,访问:localhost:8080/sec,会跳转到登陆页面,

通过下面的设置,让它返回json

.csrf().disable()// 关闭 csrf,后面讨论
    .exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        Map<String,String> map = new HashMap<>();
        map.put("errMsg", "尚未登录,请先登录");
        out.write(JSONObject.toJSONString(map));
        out.flush();
        out.close();
    }
});

重启后,访问:localhost:8080/sec

3. 退出登录

configure 方法中末尾新增:

http.logout().logoutUrl("/user/logout")
    .logoutSuccessHandler(new LogoutSuccessHandler() {
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write("退出成功");
            out.flush();
            out.close();
        }
    });

重启后,访问:localhost:8080/user/logout

4. json格式登陆

默认情况下是从 UsernamePasswordAuthenticationFilter 的attemptAuthentication方法中获取表单中的参数,

我们新建一个类,继承 UsernamePasswordAuthenticationFilter,复写 attemptAuthentication方法

从json中获取参数

/**
 * 自定义 UsernamePasswordAuthenticationFilter,从请求中获取用户名密码
 */
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        //如果请求方式不是 post,直接异常
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("请求方式有误:" + request.getMethod());
        }
        //如果请求的参数格式不是json,直接异常
        if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
            throw new AuthenticationServiceException("参数不是json:" + request.getMethod());
        }
        String username=null;
        String password = null;
        try {
            //从 json 数据中 获取用户名、密码
            Map<String,String> map = JSONObject.parseObject(request.getInputStream(),Map.class);
            username = map.get("username");//但是参数名可能不是这个,最好是用 getUsernameParameter() 方法获取参数名
            password = map.get("password");
        } catch (IOException e) {
            throw new AuthenticationServiceException("参数不对:" + request.getMethod());
        }
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        username = username.trim();
        // 封装用户名、密码,下面的 authenticate 方法会从中拿到 用户名, 调用我们的 LoginUserService 获取用户,然后比较密码
        UsernamePasswordAuthenticationToken authRequest
                = new UsernamePasswordAuthenticationToken(username,password);
        //设置ip、sessionId信息
        setDetails(request,authRequest);
        // authenticate 方法中封装了具体的密码认证逻辑
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

修改 SecurityConfig,声明我们的 MyUsernamePasswordAuthenticationFilter 的bean对象

同时使用下面新的 configure 方法,老的可以暂时注释掉

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 声明一个 PasswordEncoder ,这样数据库存储的密码就不需要 "{加密方式}",这样的前缀
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManagerBean());//认证使用
        //设置登陆成功返回值是json
        filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.write(JSONObject.toJSONString(authentication));
            }
        });
        //设置登陆失败返回值是json
        filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                Map<String,String> map = new HashMap<>();
                map.put("errMsg", exception.getMessage());
                out.write(JSONObject.toJSONString(map));
                out.flush();
                out.close();
            }
        });
        filter.setFilterProcessesUrl("/user/login");
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().csrf().disable();
        //把自定义认证过滤器加到拦截器链中
        http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

5. 获取当前登录用户

只有一行代码: SecurityContextHolder.getContext().getAuthentication()

修改 SecController

@GetMapping("/sec")
public String sec(){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    LoginUser currentUser = (LoginUser) authentication.getPrincipal();
    return JSONObject.toJSONString(currentUser);
}

登录成功后,访问:localhost:8080/sec

更新当前用户信息:SecurityContextHolder.getContext().setAuthentication(authResult)

比如:

// 可以找个地方写个这样的静态方法,如果要更新登录的用户信息,就调用这个方法
public static void setLoginUser(UserDetails userDetails) {
	SecurityContextHolder.getContext().setAuthentication(
		new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()));
}

6. 验证码

添加验证码生成的工具包

<dependency>
  <groupId>com.github.penggle</groupId>
  <artifactId>kaptcha</artifactId>
  <version>2.3.2</version>
</dependency>

修改 SecController 增加:

@GetMapping("/code")
public void getVerifyCode(HttpServletResponse resp, HttpSession session) throws IOException {
    //验证码配置
    Properties properties = new Properties();
    properties.setProperty("kaptcha.image.width", "150");
    properties.setProperty("kaptcha.image.height", "50");
    properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
    properties.setProperty("kaptcha.textproducer.char.length", "4");
    Config config = new Config(properties);
    DefaultKaptcha kaptcha = new DefaultKaptcha();
    kaptcha.setConfig(config);
    
    //生成验证码
    resp.setContentType("image/jpeg");
    String text = kaptcha.createText();
    session.setAttribute("verify_code", text);
    BufferedImage image = kaptcha.createImage(text);
    try(ServletOutputStream out = resp.getOutputStream()) {
        ImageIO.write(image, "jpg", out);
    }
}

修改 SecurityConfig

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/code").permitAll()// 验证码请求不用登录
        .anyRequest().authenticated()
        .and().csrf().disable();

    //把自定义认证过滤器加到拦截器链中
    http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}

修改 MyUsernamePasswordAuthenticationFilter

.....

String username=null;
String password = null;
String code = null;
try {
    Map<String,String> map = JSONObject.parseObject(request.getInputStream(),Map.class);
    code = map.get("code");
    username = map.get("username");
    password = map.get("password");
} catch (IOException e) {
    throw new AuthenticationServiceException("参数不对:" + request.getMethod());
}
// 校验验证码
String verify_code = (String) request.getSession().getAttribute("verify_code");
if (code == null || verify_code == null || !code.equals(verify_code)) {
    throw new AuthenticationServiceException("验证码错误");
}

.....

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前后端分离的架构中,前端和后端是独立部署和运行的,因此需要在后端项目中使用spring-boot-starter-actuator来对后端应用进行监控和管理。前端项目可以通过HTTP接口调用actuator的端点来获取应用程序的信息。 首先,你需要在后端的Spring Boot项目中添加spring-boot-starter-actuator的依赖。在你的pom.xml文件中,添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> ``` 然后,你需要在应用程序的配置文件(例如application.properties或application.yml)中配置actuator的相关属性。例如,你可以设置管理端点的访问路径和权限: ```properties # 设置管理端点的访问路径 management.endpoints.web.base-path=/actuator # 开启所有的管理端点,默认只开启了/health和/info management.endpoints.web.exposure.include=* ``` 配置完成后,你可以启动后端应用程序,并通过HTTP请求访问actuator提供的各种端点来获取应用程序的信息。例如: - `/actuator/health`:应用程序的健康状况 - `/actuator/info`:应用程序的信息 - `/actuator/metrics`:度量指标信息 - `/actuator/env`:运行时环境信息 你可以根据具体需求,选择性地暴露和配置这些端点。同时,你还可以自定义自己的管理端点,以满足特定的监控和管理需求。 在前端项目中,你可以通过发送HTTP请求来调用这些管理端点,获取后端应用程序的信息,从而实现前端对后端应用程序的监控和管理功能。 希望这些信息对你有帮助!如果还有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值