SpringSecurity6.X自定义配置-httpSecurity功能配置

前面说了用户的配置,并没有springSecurity的功能配置。这个httpSecurity就是配置SpringSecurity功能的。
还是在WebSecurityConfig.class这个类中进行HttpSecurity配置。当我们什么都不配置的时候,HttpSecurity有自己默认的配置。官方文档给出了,我们拿过来在 默认配置基础上修改:

@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 加密方法过时了,我们自己写一个
     * 官方使用的是DelegatingPasswordEncoder(代理密码加密),默认也是用的BCryptPasswordEncoder,但是会有前缀(加密方式),我们直接用BCryptPasswordEncoder
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于内存的用户认证
     * 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 内存用户管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 创建UserDetails对象,管理账号、密码、角色、权限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于数据源的用户认证
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    
    /**
     * HttpSecurity功能设置,根据功能,得到自己的过滤器链
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 权限设置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // 任何资源请求
                    .anyRequest()
                    // 都需要登陆
                    .authenticated()
            )
            // 登录页、登出页使用security提供的表单模式
            .formLogin(Customizer.withDefaults())
            // 使用账号密码这种登陆方式,有formLogin这个可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        return http.build();
    }
}

1. 自定义登陆页面

添加LoginController跳转到login.html

1.1 LoginController
@Controller
public class LoginController {
    
    @GetMapping("/tologin")
    public String login() {
        return "login";
    }

}
1.2 login.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<div th:if="${param.error}">
    错误的用户名和密码.</div>

<!--method必须为"post"-->
<!--th:action="@{/tologin}" ,
使用动态参数,表单中会自动生成_csrf隐藏字段,用于防止csrf攻击
tologin: 和登录页面保持一致即可,SpringSecurity自动进行登录认证-->
<form th:action="@{/tologin}" method="post">
    <div>
        <!--name必须为"username"-->
        <input type="text" name="username" placeholder="用户名"/>
    </div>
    <div>
        <!--name必须为"password"-->
        <input type="password" name="password" placeholder="密码"/>
    </div>
    <input type="submit" value="登录" />
</form>
</body>
</html>

因为用到了thymeleaf,所以放 templates目录下


结构
1.3 WebSecurityConfig配置

在httpSecurity中配置自定义登陆页面

@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默认密码加密过时了,说不安全
     * 我们换一个安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于内存的用户认证
     * 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 内存用户管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 创建UserDetails对象,管理账号、密码、角色、权限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于数据源的用户认证
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    
    /**
     * HttpSecurity功能设置,根据功能,得到自己的过滤器链
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 权限设置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // 任何资源请求
                    .anyRequest()
                    // 都需要登陆
                    .authenticated()
            )
            // 登录页、登出页使用security提供的表单模式
//          .formLogin(Customizer.withDefaults())
            // 自定义登陆页面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定义表单用户名参数,默认是username
                .usernameParameter("username") 
                //自定义表单密码参数,默认是password
                .passwordParameter("password") 
                //登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
                .failureUrl("/tologin?error") 
                ;
            })
            // 使用账号密码这种登陆方式,有formLogin这个可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 关闭post请求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        return http.build();
    }
}

2. 前后端分离,自定义登陆返回json数据

验证用户流程如下:


usernamepasswordauthenticationfilter

有2个类需要重写:

  • 登录成功后调用:AuthenticationSuccessHandler
  • 登录失败后调用:AuthenticationFailureHandler
2.1 MyAuthenticationSuccessHandler
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
    private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
    
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        //获取用户身份信息
        Object principal = authentication.getPrincipal();
        
        //创建结果对象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "1000");
        res.put("message", "登录成功");
        res.put("data", principal);
        
        
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);
    }

}
2.2 MyAuthenticationFailureHandler
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        //获取错误信息
        String localizedMessage = exception.getLocalizedMessage();

        //创建结果对象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "4000");
        res.put("message", localizedMessage);

        //返回响应
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);
    }

}
2.3 配置httpSecurity
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默认密码加密过时了,说不安全
     * 我们换一个安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于内存的用户认证
     * 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 内存用户管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 创建UserDetails对象,管理账号、密码、角色、权限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于数据源的用户认证
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    /**
     * HttpSecurity功能设置,根据功能,得到自己的过滤器链
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 权限设置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // /test路径 放行
                    .requestMatchers("/test/**").permitAll()
                    // /test1路径需要test1权限
                    .requestMatchers("/test1/**").hasAuthority("test1")
                    // 其他任何资源请求
                    .anyRequest()
                    // 都需要登陆
                    .authenticated()
            )
            // 登录页、登出页使用security提供的表单模式
//          .formLogin(Customizer.withDefaults())
            // 自定义登陆页面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定义表单用户名参数,默认是username
                .usernameParameter("username") 
                //自定义表单密码参数,默认是password
                .passwordParameter("password") 
                //登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
//              .failureUrl("/tologin?error")
                // 登陆成功处理
                .successHandler(new MyAuthenticationSuccessHandler())
                // 登录失败处理
                .failureHandler(new MyAuthenticationFailureHandler())
                ;
            })
            // 使用账号密码这种登陆方式,有formLogin这个可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 关闭post请求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        return http.build();
    }
}

3 自定义注销返回

3.1 MyLogoutSuccessHandler
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    
    private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        //创建结果对象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "1000");
        res.put("message", "注销成功");

        //返回响应
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);
    }

}
3.2 httpSecurity
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默认密码加密过时了,说不安全
     * 我们换一个安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于内存的用户认证
     * 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 内存用户管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 创建UserDetails对象,管理账号、密码、角色、权限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于数据源的用户认证
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    /**
     * HttpSecurity功能设置,根据功能,得到自己的过滤器链
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 权限设置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // /test路径 放行
                    .requestMatchers("/test/**").permitAll()
                    // /test1路径需要test1权限
                    .requestMatchers("/test1/**").hasAuthority("test1")
                    // 其他任何资源请求
                    .anyRequest()
                    // 都需要登陆
                    .authenticated()
            )
            // 登录页、登出页使用security提供的表单模式
//          .formLogin(Customizer.withDefaults())
            // 自定义登陆页面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定义表单用户名参数,默认是username
                .usernameParameter("username") 
                //自定义表单密码参数,默认是password
                .passwordParameter("password") 
                //登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
//              .failureUrl("/tologin?error")
                // 登陆成功处理
                .successHandler(new MyAuthenticationSuccessHandler())
                // 登录失败处理
                .failureHandler(new MyAuthenticationFailureHandler())
                ;
            })
            // 使用账号密码这种登陆方式,有formLogin这个可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 关闭post请求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        // 注销配置
        http.logout(logout -> logout.logoutSuccessHandler(new MyLogoutSuccessHandler()));
        
        return http.build();
    }
}

4. 自定义无权限请求返回

4.1 MyAuthenticationEntryPoint
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        //获取错误信息
        //String localizedMessage = authException.getLocalizedMessage();

        //创建结果对象
        //创建结果对象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "4000");
        res.put("message", "需要登录");

        //返回响应
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);

    }

}
4.2 httpSecurity
// 请求未认证的接口
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()));

5. 配置账号只允许登陆一次

当账号2次登录时,第一次登陆会失效,再操作第一次登陆的账号时,会返回我们自定义信息。

5.1 MySessionInformationExpiredStrategy
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {

    private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
    
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        //创建结果对象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "4000");
        res.put("message", "该账号已从其他设备登录");

        //返回响应
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(event.getResponse());
        httpResponseConverter.write(res, null, httpResponse);
    }

}
5.2 httpSecurity
// 账号只登陆一次
http.sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()));

SpringSecurity配置基本上好了。最后结构和配置如下:


结构

配置:

@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默认密码加密过时了,说不安全
     * 我们换一个安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于内存的用户认证
     * 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 内存用户管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 创建UserDetails对象,管理账号、密码、角色、权限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于数据源的用户认证
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    
    /**
     * HttpSecurity功能设置,根据功能,得到自己的过滤器链
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 权限设置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // /test路径 放行
                    .requestMatchers("/test/**").permitAll()
                    // /test1路径需要test1权限
                    .requestMatchers("/test1/**").hasAuthority("test1")
                    // 其他任何资源请求
                    .anyRequest()
                    // 都需要登陆
                    .authenticated()
            )
            // 登录页、登出页使用security提供的表单模式
//          .formLogin(Customizer.withDefaults())
            // 自定义登陆页面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定义表单用户名参数,默认是username
                .usernameParameter("username") 
                //自定义表单密码参数,默认是password
                .passwordParameter("password") 
                //登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
//              .failureUrl("/tologin?error")
                // 登陆成功处理
                .successHandler(new MyAuthenticationSuccessHandler())
                // 登录失败处理
                .failureHandler(new MyAuthenticationFailureHandler())
                ;
            })
            // 使用账号密码这种登陆方式,有formLogin这个可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 关闭post请求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        // 注销配置
        http.logout(logout -> logout.logoutSuccessHandler(new MyLogoutSuccessHandler()));
        
        // 错误处理
        // 请求未认证的接口
        http.exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()));
        
        // 账号只登陆一次
        http.sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()));
        
        return http.build();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容