spring boot(学习笔记第十二课)
- Spring Security内存认证,自定义认证表单
学习内容:
- Spring Security内存认证
- 自定义认证表单
1. Spring Security内存认证
- 首先开始最简单的模式,内存认证。
- 
加入 spring security的依赖。xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
- 
加入 controller进行测试。java@GetMapping("/security_hello") @ResponseBody public String hello(){ return "hello,security"; }
- 
启动应用程序。 
 默认的用户名是user,密码会在log中出现。logUsing generated security password: 9b7cd16e-af9e-4804-a6a2-9303df66ace8
- 
访问 controller,可以看到这里 * 输入上面的密码,进行login。 * 输入上面的密码,进行login。
  
 
- 
- 接着开始在内存中定义认证的用户和密码。
- 
定义内存用户,设定安全设置 @configuration。java@Configuration public class SecurityConfig { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean UserDetailsService userDetailsService() { InMemoryUserDetailsManager users = new InMemoryUserDetailsManager(); users.createUser(User.withUsername("finlay_admin") .password("123456") .roles("ADMIN") .build()); users.createUser(User.withUsername("finlay_dba") .password("123456") .roles("DBA") .build()); users.createUser(User.withUsername("finlay_super") .password("123456") .roles("ADMIN", "DBA") .build()); return users; } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth.requestMatchers ("/**")//匹配所有/** url .hasRole("ADMIN")//定义/**访问所需要ADMIN的role .anyRequest()//设定任何访问都需要认证 .authenticated()//设定任何访问都需要认证 ) .csrf(csrf -> csrf.disable())//csrf跨域访问无效 Cross-Site Request Forgery .sessionManagement(session -> session.maximumSessions(1).maxSessionsPreventsLogin(true)); return http.build(); }
- 
finlay_dba这个user只设定了DBA的role,login是无效的
  
- 
finlay_super这个user只设定了DBA的role,login是无效的
 
- 
- 进一步 测试详细的权限设定。
- 
定义 controllerjava@GetMapping("/admin/hello") @ResponseBody public String helloAdmin() { return "hello,admin"; } @GetMapping("/user/hello") @ResponseBody public String helloUser() { return "hello,user"; } @GetMapping("/db/hello") @ResponseBody public String helloDB() { return "hello,DBA"; } @GetMapping("/hello") @ResponseBody public String hello() { return "hello"; }
- 
细化各种 url的访问权限。java@Configuration public class SecurityConfig { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean UserDetailsService userDetailsService() { InMemoryUserDetailsManager users = new InMemoryUserDetailsManager(); users.createUser(User.withUsername("finlay_user") .password("123456") .roles("USER") .build()); users.createUser(User.withUsername("finlay_admin") .password("123456") .roles("ADMIN") .build()); users.createUser(User.withUsername("finlay_dba") .password("123456") .roles("DBA") .build()); users.createUser(User.withUsername("finlay_super") .password("123456") .roles("ADMIN", "DBA") .build()); return users; } @Bean SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeHttpRequests( auth -> auth.requestMatchers("/admin/**")//匹配所有/** url .hasRole("ADMIN")//只能对于admin的role,才能访问 .requestMatchers("/user/**")//匹配/user/** .hasRole("USER")//只有对于user的role,才能访问 .requestMatchers("/db/**")//配置/db/** .hasRole("DBA")//只有对于dba的role,才能访问 .anyRequest() .authenticated()//设定任何访问都需要认证 ) .formLogin(form -> form.loginProcessingUrl("/login")//这里对于前后端分离,提供的非页面访问url .usernameParameter("username")//页面上form的用户名 .passwordParameter("password"))//页面上form的密码 .csrf(csrf -> csrf.disable())//csrf跨域访问无效 .sessionManagement(session -> session.maximumSessions(1).maxSessionsPreventsLogin(true)); return httpSecurity.build(); } }
- 
尝试访问 /db/hello。
  
- 
清除 chrome浏览器的session数据。
 因为没有定义logout功能,所以每次login成功之后,都不能消除login情报,这时候可以在chrome浏览器直接使用快捷键ctrl-shift-del之后进行session情报的删除。这样能够进行测试。 
 
- 
2. 自定义认证表单
通常的情况是不用spring security默认提供的页面,下面进行自定义认证页面。
- 
继续在 SecurityConfig,controller以及html中进行配置`- 
配置自定义的认证 urljava@Bean SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeHttpRequests(auth -> auth.requestMatchers("/login*") .permitAll() .requestMatchers("/admin/**")//匹配所有/** url .hasRole("ADMIN")//只能对于admin的role,才能访问 .requestMatchers("/user/**")//匹配/user/** .hasRole("USER")//只有对于user的role,才能访问 .requestMatchers("/db/**")//配置/db/** .hasRole("DBA")//只有对于dba的role,才能访问 .anyRequest() .authenticated()//设定任何访问都需要认证 ) .formLogin(form -> form.loginPage("/loginPage") .loginProcessingUrl("/doLogin")//这里对于前后端分离,提供的非页面访问url .usernameParameter("uname")//页面上form的用户名 .passwordParameter("passwd") .successHandler(new SuccessHandler())//认证成功的处理 .failureHandler(new FailureHandler())//认证失败的处理 .defaultSuccessUrl("/index")//默认的认证之后的页面 .failureForwardUrl("/loginPasswordError"))//默认的密码失败之后的页面 .exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler())) .csrf(csrf -> csrf.disable())//csrf跨域访问无效 .sessionManagement(session -> session.maximumSessions(1).maxSessionsPreventsLogin(true)); return httpSecurity.build(); }- 
对于认证画面的 url,进行permitAll开放,因为对于认证画面,不需要进行认证。javaauth.requestMatchers("/login*") .permitAll()
- 
定义login的认证画面url,之后会定义 controller和view注意,仅限于前后端一体程序java.formLogin(form -> form.loginPage("/loginPage")
- 
定于处理认证请求的 url注意前后端一体和前后端分离程序都会使用用这个url,springboot不对这个url定义controllerjava.loginProcessingUrl("/doLogin")//这里对于前后端分离,提供的非页面访问url
- 
对自定义页面的 input进行设定,这里之后的html会使用。java.usernameParameter("uname")//页面上form的用户名 .passwordParameter("passwd")
- 
对于前后端的分离应用,直接访问 doLogin,通过这里给前端返回结果对这两个类详细说明。java.successHandler(new SuccessHandler())//认证成功的处理 .failureHandler(new FailureHandler())//认证失败的处理
- 
如果直接访问 loginPage,那么认证成功后默认的url就是这里
 注意,和successForwardUrl的区别是,successForwardUrl是post请求,这里是get请求java.defaultSuccessUrl("/index")//默认的认证之后的页面
- 
配置密码失败之后的 urljava.failureForwardUrl("/loginPasswordError"))//默认的密码失败之后的页面
- 
配置没有权限时候的错误页面的 url,之后对CustomizeAccessDeniedHandler类进行说明。javaexceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
- 
success handler类进行定义,主要在前后端的程序中使用。java//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<String, Object> 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类进行定义,主要在前后端的程序中使用。java//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", "登陆失败"); } ObjectMapper om = new ObjectMapper(); printWriter.write(om.writeValueAsString(map)); printWriter.flush(); printWriter.close(); }
- 
对 CustomizeAccessDeniedHandler类进行定义,对没有权限等情况进行定义。比如,需要的role是ADMIN,但是认证结束后的role确是DBA。javaprivate static class CustomizeAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.sendRedirect("/loginNoPermissionError"); } }
 
- 
- 
在 controller层做出一个LoginController注意,为了定义permitAll方便,统一采用login`开头java@Controller public class LoginController { @GetMapping("/loginPage") public String loginPage() { return "login"; } @GetMapping("/loginNoPermissionError") public String loginNoPermission() { return "no_permission_error"; } @GetMapping("/loginPasswordError") public String loginError(Model model) { model.addAttribute("message", "认证失败"); return "password_error"; } @PostMapping("/loginPasswordError") public String loginErrorPost(Model model) { model.addAttribute("message", "认证失败"); return "password_error"; } }
- 
定义 view层的各个html注意,前后端分离程序- 
login的认证画面viewhtml<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org/" lang="en"> <head> <meta charset="UTF-8"> <title>Spring Security 用户自定义认证画面</title> </head> <body> <h1>自定义用户登陆</h1> <form th:action="@{/doLogin}" method="post"> 用户名:<input type="text" name="uname"><br> 密码:<input type="text" name="passwd"><br> <input type="submit" value="登陆"> </form> </body> </html>
- 
定义密码错误的认证错误画面 viewhtml<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org/" lang="en"> <head> <meta charset="UTF-8"> <title>Spring Security 用户自定义-密码输入错误</title> </head> <body> <h1>自定义用户登陆错误-用户密码输入错误"</h1> </form> </body> </html>
- 
定义没有权限的认证错误画面 view,比如需要ADMIN的role,但是用户只有DBA的rolehtml<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org/" lang="en"> <head> <meta charset="UTF-8"> <title>Spring Security 用户自定义-权限不足</title> </head> <body> <h1>"用户自定义画面,权限不足"</h1> </form> </body> </html>
 
- 
 
- 
- 
验证结果 - DBA用户访问- http://localhost:8080/db/hello 
- 输入错误密码
  
- USER的- role用户登录,但是访问- http://localhost:8080/db/hello,需要- DBA的- role
 