spring boot(学习笔记第十三课)

简介: 【7月更文挑战第14天】
  1. 传统后端开发模式 vs 前后端分离模式
    传统后端开发模式
    上面主要练习传统后端开发模式,在这种模式下,页面的渲染都是请求后端,在后端完成页面的渲染。认证的页面都是通过https://localhost:8080/loginPage进行用户名和密码的form填写,之后重定向到需要认证的资源的页面。
    正如spring boot(学习笔记第十二课)的练习的那样,在传统后端开发模式,需要配置各种页面.
    .formLogin(form -> form.loginPage("/loginPage")
       .loginProcessingUrl("/doLogin")//这里的url不用使用controller进行相应,spring security自动处理
       .usernameParameter("uname")//页面上form的用户名
       .passwordParameter("passwd")
       .defaultSuccessUrl("/index")//默认的认证之后的页面
       .failureForwardUrl("/loginPasswordError"))//默认的密码失败之后的页面
    
    AI 代码解读
    .exceptionHandling(exceptionHandling ->
                     exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
    
    AI 代码解读
    1
    2
    3
    4
    5
    6
    7
    8
    前后端分离开发模式
    现在web application的已经过渡到了前后端分离开发模式,而spring boot security也兼容这种模式。

接下来通过使用postman,模拟下前后端分离模式的spring security开发和使用场景。
指定认证成功和失败的handler
注意,这里一定要去掉 .loginPage("/loginPage")
.formLogin(form -> form.loginProcessingUrl("/loginProcess")//这里对于前后端分离,提供的非页面访问url
.usernameParameter("uname")
.passwordParameter("passwd")
.successHandler(new SuccessHandler())
.failureHandler(new FailureHandler()))
1
2
3
4
5
定义认证成功和失败的handler
//success handler
private static class SuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication
) throws IOException {
Object principal = authentication.getPrincipal();
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter printWriter = httpServletResponse.getWriter();
httpServletResponse.setStatus(200);
Map map = new HashMap<>();
map.put("status", 200);
map.put("msg", principal);
ObjectMapper om = new ObjectMapper();
printWriter.write(om.writeValueAsString(map));
printWriter.flush();
printWriter.close();
}
}

//failure handler
private static class FailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            AuthenticationException authenticationException
    ) throws IOException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter printWriter = httpServletResponse.getWriter();
        httpServletResponse.setStatus(401);
        Map<String, Object> map = new HashMap<>();
        map.put("status", 401);
        if (authenticationException instanceof LockedException) {
            map.put("msg", "账户被锁定,登陆失败");
        } else if (authenticationException instanceof BadCredentialsException) {
            map.put("msg", "账户输入错误,登陆失败");
        } else {
            map.put("msg", authenticationException.toString());
        }
        ObjectMapper om = new ObjectMapper();
        printWriter.write(om.writeValueAsString(map));
        printWriter.flush();
        printWriter.close();
    }
AI 代码解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
一定要将/loginProcess的permitAll打开。注意,这里的习惯是将认证相关的url都定义成login开头的,并且一起进行/login的permitAll设定
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(auth ->
auth.requestMatchers("/login
")
.permitAll()
1
2
3
4
5
使用postman进行认证测试。
pattern-1 正确的密码和用户名
这里使用http://localhost:8080/loginProcess?uname=finlay_user&passwd=123456进行访问。注意,一定要是用post,不能使用get。
这里看到SuccessHandler

pattern-2 错误的密码和用户名

认证成功,但是访问资源权限不够,需要设置exceptionHandling。
设置 exceptionHandling.accessDeniedHandler
.exceptionHandling(exceptionHandling ->
exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
1
2
定义 exceptionHandler
注意,在上一课传统后端开发模式的时候,定义的是redirect到画面,但是前后端分离模式,定义JSON返回值
传统后端开发模式
// 传统后端开发模式
private static class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect("/loginNoPermissionError");
}
}
1
2
3
4
5
6
7
传统前后端分离开发模式(JSON返回)
// 传统前后端开发模式
private static class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect("/loginNoPermissionError");
}
}
1
2
3
4
5
6
7
访问/loginProcess,使用finlay_user(ROLE==user)进行登录

访问/db/hello,这里需要ROLE==DBA)进行登录,但是目前的httpSession不满足条件。

  1. Spring Security的logout功能
    这里httpSession的如果需要logout,这里练习如何进行logout动作。

传统后端开发模式如何开发logout
注意,这里传统后端开发模式需要将successHandler,failureHandler和logoutSuccessHandler都注释掉,否则,这个的对应的url设置都会无效
.formLogin(form ->
form.loginProcessingUrl("/loginProcess")//这里对于前后端分离,提供的非页面访问url
.usernameParameter("uname")
.passwordParameter("passwd")
.loginPage("/loginPage")
.failureForwardUrl("/loginPasswordError")
.successForwardUrl("/index"))
// .successHandler(new SuccessHandler())
// .failureHandler(new FailureHandler()))
.logout(httpSecurityLogoutConfigurer ->
httpSecurityLogoutConfigurer.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.logoutSuccessUrl("/loginPage"))
// .logoutSuccessHandler(new MyLogoutHandler()))
.exceptionHandling(exceptionHandling ->
exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
.csrf(csrf -> csrf.disable())//csrf跨域访问无效
.sessionManagement(session -> session
.maximumSessions(-1)
.maxSessionsPreventsLogin(true));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
设置logout处理的url
.logoutUrl(“/logout”),这里的/logouot不需要进行对应,spring boot security会进行响应处理。
对logout进行处理
.logout(httpSecurityLogoutConfigurer ->
httpSecurityLogoutConfigurer.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.logoutSuccessUrl("/loginPage"))
1
2
3
4
5
clearAuthentication 是 Spring Security 中的一个方法,用于清除当前用户的认证信息,即使当前用户注销登录。在 SecurityContextHolder 中保存的 SecurityContext 对象将被清除,这意味着在下一次调用 SecurityContextHolder.getContext() 时,将不再有认证信息。
.invalidateHttpSession(true)是将httpSession删除,彻底进行logout。
.logoutSuccessUrl("/loginPage"))调用将重定向到行的页面/logoutPage,这里是使用登录的页面。注意,这里如果调用.logoutSuccessHandler(new MyLogoutHandler())进行设定的话,就是使用前后端分离开发模式,logoutSuccessUrl("/loginPage")即便设置也会无效。
设置logout处理页面(controller) 在页面上表示登录用户的用户名
@GetMapping("/logoutPage")
public String logoutPage(Model model) {
String userName = "anonymous";
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
if (authentication.getName() != null) {
userName = authentication.getName();
}
}
model.addAttribute("login_user",userName);
return "logout";
}
1
2
3
4
5
6
7
8
9
10
11
12
设置logout处理页面(html)
<!DOCTYPE html>









1
2
3
4
5
6
7
8
9
10
11
12
13
使用logout功能进行logout

在显示logout按钮的同时,也显示出了Authentication authentication = SecurityContextHolder.getContext().getAuthentication();取出来的login_user名字。
点击logout按钮,成功后返回 .logoutSuccessUrl("/loginPage"))
前后端分离开发模式如何开发logout
将 .logoutSuccessUrl("/loginPage"))替换成 .logoutSuccessHandler(new MyLogoutHandler()))

.logout(httpSecurityLogoutConfigurer ->
httpSecurityLogoutConfigurer.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
// .logoutSuccessUrl("/loginPage"))
.logoutSuccessHandler(new MyLogoutHandler()))
1
2
3
4
5
6
定义MyLogoutHandler将logout结果包装成JSON格式,传给前端。

private static class MyLogoutHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request
            , HttpServletResponse response
            , Authentication authentication) throws IOException {
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 使会话失效
            session.invalidate();
        }
        response.setContentType("application/json;charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        response.setStatus(200);
        Map<String, Object> map = new HashMap<>();
        map.put("status", 200);
        map.put("msg", "logout OK");
        ObjectMapper om = new ObjectMapper();
        printWriter.write(om.writeValueAsString(map));
        printWriter.flush();
        printWriter.close();
    }
}
AI 代码解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果logout完毕了,没有有效httpSession,那么访问/db/hello资源的话,怎么让spring security返回JSON,让前端框架接收到呢。这里需要AuthenticationEntryPoint。

设定AuthenticationEntryPoint
.logout(httpSecurityLogoutConfigurer ->
httpSecurityLogoutConfigurer.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
// .logoutSuccessUrl("/loginPage"))
.logoutSuccessHandler(new MyLogoutHandler()))
.exceptionHandling(exceptionHandling ->
exceptionHandling
.accessDeniedHandler(new CustomizeAccessDeniedHandler())
.authenticationEntryPoint(new RestAuthenticationEntryPoint()))
1
2
3
4
5
6
7
8
9
10
定义AuthenticationEntryPoint
private static class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");

        String body = "{\"error\":\"Not Authenticated\"}";
        OutputStream out = response.getOutputStream();
        out.write(body.getBytes());
        out.flush();
    }
}
AI 代码解读

1
2
3
4
5
6
7
8
9
10
11
12
使用postman模拟前端进行login。

模拟前端调用/logout进行logout。

模拟前端调用/db/hello进行没有httpSession的访问,期待返回authenciationError的JSON应答。

目录
打赏
0
2
3
1
207
分享
相关文章
什么是阻塞IO?
**阻塞IO是一种IO操作模式,使得调用线程在IO未完成时会暂停,等待操作完成。简单但可能导致线程阻塞,适用于低并发、长处理场景。Java示例中,`ServerSocket`和`Socket`展示了这种模式。服务端接收到客户端连接后读取数据,回应&quot;Echo&quot;,每个连接需单独线程处理。高并发时可考虑非阻塞IO(NIO)或异步IO来优化。**
148 6
Java虚拟机(JVM)性能调优实战指南
在追求软件开发卓越的征途中,Java虚拟机(JVM)性能调优是一个不可或缺的环节。本文将通过具体的数据和案例,深入探讨JVM性能调优的理论基础与实践技巧,旨在为广大Java开发者提供一套系统化的性能优化方案。文章首先剖析了JVM内存管理机制的工作原理,然后通过对比分析不同垃圾收集器的适用场景及性能表现,为读者揭示了选择合适垃圾回收策略的数据支持。接下来,结合线程管理和JIT编译优化等高级话题,文章详细阐述了如何利用现代JVM提供的丰富工具进行问题诊断和性能监控。最后,通过实际案例分析,展示了性能调优过程中可能遇到的挑战及应对策略,确保读者能够将理论运用于实践,有效提升Java应用的性能。 【
374 10
【vue】 在vue2项目中使用echarts
【vue】 在vue2项目中使用echarts
458 0
echarts 热力图(中国地图版)
echarts 热力图(中国地图版)
683 0
【综合能源】含氢气氨气综合能源系统优化调度【免费】
该程序参考《_基于氨储能技术的电转氨耦合风–光–火综合能源系统双层优化调度》模型,对制氨工厂、风力发电、电制氢、燃气轮机、火电机组等主体进行建模分析,以火电机组启停成本、煤耗成本、弃风成本和购气成本形成的综合成本为目标,程序采用matlab+cplex求解,注释清晰,方便学习!
深度学习在医疗影像分析中的应用与挑战
随着人工智能技术的飞速发展,深度学习已成为医学影像分析领域的一股不可忽视的力量。通过构建复杂的神经网络模型,深度学习能够处理和分析大量的高维度数据,如X光、MRI和CT扫描图像,实现对疾病标记的自动检测和诊断。本文将探讨深度学习技术在医疗影像分析中的实际应用案例,包括癌症检测、神经退行性疾病的早期发现以及心脏病的预测等,并讨论当前面临的主要挑战,如数据集的质量和多样性不足、模型解释性差、以及隐私保护等问题。 【7月更文挑战第15天】
119 11
安卓与iOS开发环境的差异与挑战
【7月更文挑战第15天】在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚,它们不仅在用户体验上有所区别,更在开发环境上呈现出截然不同的面貌。本文将深入探讨这两个平台的开发环境差异,并分析开发者面临的挑战。我们将从开发工具、编程语言、用户界面设计、性能优化以及跨平台开发框架等方面进行比较,旨在为开发者提供一个全面的视角,帮助他们更好地适应不同平台的开发需求。通过对比分析,我们希望能够揭示出各自平台的优势与不足,并为未来的发展趋势提供预测。
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等