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")@ResponseBodypublic String hello(){return "hello,security";}
- 启动应用程序。
默认的用户名是user
,密码会在log中出现。Using generated security password: 9b7cd16e-af9e-4804-a6a2-9303df66ace8
- 访问
controller
,可以看到这里 * 输入上面的密码,进行login。
- 加入
- 接着开始在内存中定义认证的用户和密码。
- 定义内存用户,设定安全设置
@configuration
。@Configuration public class SecurityConfig {@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@BeanUserDetailsService 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;}@BeanSecurityFilterChain 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")@ResponseBodypublic String helloAdmin() {return "hello,admin";}@GetMapping("/user/hello")@ResponseBodypublic String helloUser() {return "hello,user";}@GetMapping("/db/hello")@ResponseBodypublic String helloDB() {return "hello,DBA";}@GetMapping("/hello")@ResponseBodypublic String hello() {return "hello";}
- 细化各种
url
的访问权限。@Configuration public class SecurityConfig {@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@BeanUserDetailsService 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;}@BeanSecurityFilterChain 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
@BeanSecurityFilterChain 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 handlerprivate static class SuccessHandler implements AuthenticationSuccessHandler {@Overridepublic 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 handlerprivate static class FailureHandler implements AuthenticationFailureHandler {@Overridepublic 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 {@Overridepublic 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