Series data
The latest version of spring security5 X tutorial # blog
The latest version of spring security5 Tutorial # source code #
note
spring security flowchart
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-zgyd8qbp-1621588647483)( https://note.youdao.com/yws/api/personal/file/WEB43ab8e6d7cff0e1d1d48a177991f9e4d?method=download&shareKey=b4086a495e5665741d5ce836ca6cae90 )]
Configure login page
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") .permitAll() .and() .csrf().disable(); }
Resolution:
- The loginPage("/login.html") configuration login page points to / static / login HTML (default);
- In fact, it also has a hidden operation, that is, the login interface address is also set to / login HTML. In other words, the new login page and login interface address are / login html. They can also be separated and passed loginPage("/login.html").loginProcessingUrl("/doLogin"). See FormLoginConfigurer for specific logic;
Replace security default login
Add login. In the static directory Add viewController page or HTML:
@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); }
Resolution:
For the default login logic, see the logic of DefaultLoginPageGeneratingFilter class;
Modify login parameters
The default parameter names are username and password. Modify the front-end parameter name through the following configuration:
.and() .formLogin() .loginPage("/login.html") .loginProcessingUrl("/doLogin") .usernameParameter("name") .passwordParameter("passwd") .permitAll()
Login event callback
success
- defaultSuccessUrl: the default success page. If there is a history jump, jump to history
- successForwardUrl: indicates that no matter where you come from, you will jump to the address specified by successForwardUrl after logging in.
Note: in practice, only one of defaultSuccessUrl and successForwardUrl needs to be configured.
fail
Logout login
The default interface for logout is / logout, which can also be configured.
.and() .logout() .logoutUrl("/logout") .logoutRequestMatcher(new AntPathRequestMatcher("/logout","POST")) .logoutSuccessUrl("/index") .deleteCookies() .clearAuthentication(true) .invalidateHttpSession(true) .permitAll() .and()
explain:
- The default logout URL is / logout, which is a GET request. We can modify the default logout URL through the logoutUrl method.
- The logoutRequestMatcher method can not only modify the logout URL, but also modify the request method. In the actual project, you can set any one of this method and logoutUrl.
- logoutSuccessUrl indicates the page to jump to after successful logout.
- deleteCookies is used to clear cookies.
- clearAuthentication and invalidehttpsession respectively mean clearing authentication information and invalidating HttpSession. By default, it can be cleared without configuration.
Front and back end separation, json login
- Custom login authentication filter;
public class LoginFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String verify_code = (String) request.getSession().getAttribute("verify_code"); if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) { Map<String, String> loginData = new HashMap<>(); try { loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class); } catch (IOException e) { }finally { String code = loginData.get("code"); checkCode(response, code, verify_code); } String username = loginData.get(getUsernameParameter()); String password = loginData.get(getPasswordParameter()); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } else { checkCode(response, request.getParameter("code"), verify_code); return super.attemptAuthentication(request, response); } } public void checkCode(HttpServletResponse resp, String code, String verify_code) { if (code == null || verify_code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) { //Incorrect verification code throw new AuthenticationServiceException("Incorrect verification code"); } } }
- Inject bean s;
@Bean LoginFilter loginFilter() throws Exception { LoginFilter loginFilter = new LoginFilter(); loginFilter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); Hr hr = (Hr) authentication.getPrincipal(); hr.setPassword(null); RespBean ok = RespBean.ok("Login successful!", hr); String s = new ObjectMapper().writeValueAsString(ok); out.write(s); out.flush(); out.close(); } }); loginFilter.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(); RespBean respBean = RespBean.error(exception.getMessage()); if (exception instanceof LockedException) { respBean.setMsg("The account is locked, please contact the administrator!"); } else if (exception instanceof CredentialsExpiredException) { respBean.setMsg("The password has expired, please contact the administrator!"); } else if (exception instanceof AccountExpiredException) { respBean.setMsg("The account has expired, please contact the administrator!"); } else if (exception instanceof DisabledException) { respBean.setMsg("The account is disabled, please contact the administrator!"); } else if (exception instanceof BadCredentialsException) { respBean.setMsg("Wrong user name or password, please re-enter!"); } out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } }); loginFilter.setAuthenticationManager(authenticationManagerBean()); loginFilter.setFilterProcessesUrl("/doLogin"); return loginFilter; }
- Replace default filter:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() ... //ellipsis http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class); }
Verification code function
- Obtain the verification code through the interface and put the verification code into the session;
- Customize the verification code filter (verify whether the input is consistent with that in the session), and place the verification code filter before the UsernamePasswordAuthenticationFilter;
@Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(captchaAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
Role access
Role permission interception
http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .antMatchers("/user/**").hasRole("user") .anyRequest().authenticated() .and() ... ...
Role permission inheritance
Superior roles must be able to access subordinate resources;
@Bean RoleHierarchy roleHierarchy() { RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); hierarchy.setHierarchy("ROLE_admin > ROLE_user"); return hierarchy; }
Note that during configuration, roles need to be added manually_ Prefix. The above configuration represents ROLE_admin automatically has ROLE_user permissions.
Persistent user data
UserDetailService
Spring Security supports many different data sources, which will eventually be encapsulated into instances of UserDetailsService
[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-mwuw5hk5-1621588647485)( https://note.youdao.com/yws/api/personal/file/WEB638f4f116aaec88dce24e6790a375492?method=download&shareKey=e66b234b4f4246c0f1525fa573b91ba2 )]
It can be seen that among several implementation classes that can be used directly, in addition to InMemoryUserDetailsManager, there is also a JdbcUserDetailsManager. Using JdbcUserDetailsManager allows us to connect the database with Spring Security through JDBC.
JdbcUserDetailsManager
JdbcUserDetailsManager itself provides a database model, which is saved in org / springframework / security / core / userdetails / JDBC / users ddl;
Resolution:
- The users table stores the basic information of users, including user name, user password and whether the account is available.
- The user's role is saved in the authorities.
- authorities and users are associated through username.
Inject a userDetailService to replace the default InMemoryUserDetailsManager:
@Autowired DataSource dataSource; @Override @Bean protected UserDetailsService userDetailsService() { JdbcUserDetailsManager manager = new JdbcUserDetailsManager(); manager.setDataSource(dataSource); if (!manager.userExists("u0")) { manager.createUser(User.withUsername("u0").password("123").roles("admin").build()); } if (!manager.userExists("u1")) { manager.createUser(User.withUsername("u1").password("123").roles("user").build()); } return manager; }
Remember that users log in automatically
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .rememberMe() .and() .csrf().disable(); }