spring boot(学习笔记第十二课)
- Spring Security内存认证,自定义认证表单
学习内容:
- Spring Security内存认证
- 自定义认证表单
1. Spring Security内存认证
- 首先开始最简单的模式,内存认证。
- 加入
spring security
的依赖。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
- 加入
controller
进行测试。@GetMapping("/security_hello") @ResponseBody public String hello(){ return "hello,security"; }
- 启动应用程序。
默认的用户名是user
,密码会在log中出现。Using generated security password: 9b7cd16e-af9e-4804-a6a2-9303df66ace8
- 访问
controller
,可以看到这里 * 输入上面的密码,进行login。
- 加入
- 接着开始在内存中定义认证的用户和密码。
- 定义内存用户,设定安全设置
@configuration
。@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是无效的
- 定义内存用户,设定安全设置
- 进一步 测试详细的权限设定。
- 定义
controller
@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
的访问权限。@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
中进行配置`- 配置自定义的认证
url
@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
开放,因为对于认证画面,不需要进行认证。auth.requestMatchers("/login*") .permitAll()
- 定义login的认证画面url,之后会定义
controller
和view
注意,仅限于前后端一体程序.formLogin(form -> form.loginPage("/loginPage")
- 定于处理认证请求的
url
注意前后端一体和前后端分离程序都会使用用这个url
,springboot不对这个url定义controller
.loginProcessingUrl("/doLogin")//这里对于前后端分离,提供的非页面访问url
- 对自定义页面的
input
进行设定,这里之后的html
会使用。.usernameParameter("uname")//页面上form的用户名 .passwordParameter("passwd")
- 对于前后端的分离应用,直接访问
doLogin
,通过这里给前端返回结果对这两个类详细说明。.successHandler(new SuccessHandler())//认证成功的处理 .failureHandler(new FailureHandler())//认证失败的处理
- 如果直接访问
loginPage
,那么认证成功后默认的url
就是这里
注意,和successForwardUrl
的区别是,successForwardUrl
是post
请求,这里是get
请求.defaultSuccessUrl("/index")//默认的认证之后的页面
- 配置密码失败之后的
url
.failureForwardUrl("/loginPasswordError"))//默认的密码失败之后的页面
- 配置没有权限时候的错误页面的
url
,之后对CustomizeAccessDeniedHandler
类进行说明。exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
success 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<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
类进行定义,主要在前后端的程序中使用。//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
。private 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`开头@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
的认证画面view
<!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>
- 定义密码错误的认证错误画面
view
<!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
的role
<!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