spring security + spring session + spring oauth2 notes

Posted by slickboyj on Tue, 08 Feb 2022 23:13:06 +0100

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:

  1. The loginPage("/login.html") configuration login page points to / static / login HTML (default);
  2. 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

  1. 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");
        }
    }
}
  1. 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;
}
  1. Replace default filter:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        ...
        //ellipsis
    http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
}

Verification code function

  1. Obtain the verification code through the interface and put the verification code into the session;
  2. 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();
}

Topics: Spring Security