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

前面說了用戶的配置,并沒有springSecurity的功能配置。這個httpSecurity就是配置SpringSecurity功能的。
還是在WebSecurityConfig.class這個類中進(jìn)行HttpSecurity配置。當(dāng)我們什么都不配置的時候,HttpSecurity有自己默認(rèn)的配置。官方文檔給出了,我們拿過來在 默認(rèn)配置基礎(chǔ)上修改:

@Configuration
// mvc項目需要手動加這個注解,Springboot項目在我們引入security時自動加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 加密方法過時了,我們自己寫一個
     * 官方使用的是DelegatingPasswordEncoder(代理密碼加密),默認(rèn)也是用的BCryptPasswordEncoder,但是會有前綴(加密方式),我們直接用BCryptPasswordEncoder
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于內(nèi)存的用戶認(rèn)證
     * 注意 有自定義配置后,application.yml文件中配置的賬號密碼就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 內(nèi)存用戶管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 創(chuàng)建UserDetails對象,管理賬號、密碼、角色、權(quán)限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于數(shù)據(jù)源的用戶認(rèn)證
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    
    /**
     * HttpSecurity功能設(shè)置,根據(jù)功能,得到自己的過濾器鏈
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 權(quán)限設(shè)置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // 任何資源請求
                    .anyRequest()
                    // 都需要登陸
                    .authenticated()
            )
            // 登錄頁、登出頁使用security提供的表單模式
            .formLogin(Customizer.withDefaults())
            // 使用賬號密碼這種登陸方式,有formLogin這個可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        return http.build();
    }
}

1. 自定義登陸頁面

添加LoginController跳轉(zhuǎn)到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}" ,
使用動態(tài)參數(shù),表單中會自動生成_csrf隱藏字段,用于防止csrf攻擊
tologin: 和登錄頁面保持一致即可,SpringSecurity自動進(jìn)行登錄認(rèn)證-->
<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目錄下


結(jié)構(gòu)
1.3 WebSecurityConfig配置

在httpSecurity中配置自定義登陸頁面

@Configuration
// mvc項目需要手動加這個注解,Springboot項目在我們引入security時自動加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默認(rèn)密碼加密過時了,說不安全
     * 我們換一個安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于內(nèi)存的用戶認(rèn)證
     * 注意 有自定義配置后,application.yml文件中配置的賬號密碼就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 內(nèi)存用戶管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 創(chuàng)建UserDetails對象,管理賬號、密碼、角色、權(quán)限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于數(shù)據(jù)源的用戶認(rèn)證
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    
    /**
     * HttpSecurity功能設(shè)置,根據(jù)功能,得到自己的過濾器鏈
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 權(quán)限設(shè)置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // 任何資源請求
                    .anyRequest()
                    // 都需要登陸
                    .authenticated()
            )
            // 登錄頁、登出頁使用security提供的表單模式
//          .formLogin(Customizer.withDefaults())
            // 自定義登陸頁面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定義表單用戶名參數(shù),默認(rèn)是username
                .usernameParameter("username") 
                //自定義表單密碼參數(shù),默認(rèn)是password
                .passwordParameter("password") 
                //登錄失敗的返回地址,加了參數(shù),是因為有這個參數(shù),頁面會有提示
                .failureUrl("/tologin?error") 
                ;
            })
            // 使用賬號密碼這種登陸方式,有formLogin這個可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 關(guān)閉post請求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        return http.build();
    }
}

2. 前后端分離,自定義登陸返回json數(shù)據(jù)

驗證用戶流程如下:


usernamepasswordauthenticationfilter

有2個類需要重寫:

  • 登錄成功后調(diào)用:AuthenticationSuccessHandler
  • 登錄失敗后調(diào)用: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();
        
        //創(chuàng)建結(jié)果對象
        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();

        //創(chuàng)建結(jié)果對象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "4000");
        res.put("message", localizedMessage);

        //返回響應(yīng)
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);
    }

}
2.3 配置httpSecurity
@Configuration
// mvc項目需要手動加這個注解,Springboot項目在我們引入security時自動加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默認(rèn)密碼加密過時了,說不安全
     * 我們換一個安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于內(nèi)存的用戶認(rèn)證
     * 注意 有自定義配置后,application.yml文件中配置的賬號密碼就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 內(nèi)存用戶管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 創(chuàng)建UserDetails對象,管理賬號、密碼、角色、權(quán)限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于數(shù)據(jù)源的用戶認(rèn)證
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    /**
     * HttpSecurity功能設(shè)置,根據(jù)功能,得到自己的過濾器鏈
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 權(quán)限設(shè)置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // /test路徑 放行
                    .requestMatchers("/test/**").permitAll()
                    // /test1路徑需要test1權(quán)限
                    .requestMatchers("/test1/**").hasAuthority("test1")
                    // 其他任何資源請求
                    .anyRequest()
                    // 都需要登陸
                    .authenticated()
            )
            // 登錄頁、登出頁使用security提供的表單模式
//          .formLogin(Customizer.withDefaults())
            // 自定義登陸頁面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定義表單用戶名參數(shù),默認(rèn)是username
                .usernameParameter("username") 
                //自定義表單密碼參數(shù),默認(rèn)是password
                .passwordParameter("password") 
                //登錄失敗的返回地址,加了參數(shù),是因為有這個參數(shù),頁面會有提示
//              .failureUrl("/tologin?error")
                // 登陸成功處理
                .successHandler(new MyAuthenticationSuccessHandler())
                // 登錄失敗處理
                .failureHandler(new MyAuthenticationFailureHandler())
                ;
            })
            // 使用賬號密碼這種登陸方式,有formLogin這個可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 關(guān)閉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 {
        //創(chuàng)建結(jié)果對象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "1000");
        res.put("message", "注銷成功");

        //返回響應(yīng)
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);
    }

}
3.2 httpSecurity
@Configuration
// mvc項目需要手動加這個注解,Springboot項目在我們引入security時自動加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默認(rèn)密碼加密過時了,說不安全
     * 我們換一個安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于內(nèi)存的用戶認(rèn)證
     * 注意 有自定義配置后,application.yml文件中配置的賬號密碼就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 內(nèi)存用戶管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 創(chuàng)建UserDetails對象,管理賬號、密碼、角色、權(quán)限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于數(shù)據(jù)源的用戶認(rèn)證
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    /**
     * HttpSecurity功能設(shè)置,根據(jù)功能,得到自己的過濾器鏈
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 權(quán)限設(shè)置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // /test路徑 放行
                    .requestMatchers("/test/**").permitAll()
                    // /test1路徑需要test1權(quán)限
                    .requestMatchers("/test1/**").hasAuthority("test1")
                    // 其他任何資源請求
                    .anyRequest()
                    // 都需要登陸
                    .authenticated()
            )
            // 登錄頁、登出頁使用security提供的表單模式
//          .formLogin(Customizer.withDefaults())
            // 自定義登陸頁面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定義表單用戶名參數(shù),默認(rèn)是username
                .usernameParameter("username") 
                //自定義表單密碼參數(shù),默認(rèn)是password
                .passwordParameter("password") 
                //登錄失敗的返回地址,加了參數(shù),是因為有這個參數(shù),頁面會有提示
//              .failureUrl("/tologin?error")
                // 登陸成功處理
                .successHandler(new MyAuthenticationSuccessHandler())
                // 登錄失敗處理
                .failureHandler(new MyAuthenticationFailureHandler())
                ;
            })
            // 使用賬號密碼這種登陸方式,有formLogin這個可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 關(guān)閉post請求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        // 注銷配置
        http.logout(logout -> logout.logoutSuccessHandler(new MyLogoutSuccessHandler()));
        
        return http.build();
    }
}

4. 自定義無權(quán)限請求返回

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();

        //創(chuàng)建結(jié)果對象
        //創(chuàng)建結(jié)果對象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "4000");
        res.put("message", "需要登錄");

        //返回響應(yīng)
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponseConverter.write(res, null, httpResponse);

    }

}
4.2 httpSecurity
// 請求未認(rèn)證的接口
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()));

5. 配置賬號只允許登陸一次

當(dāng)賬號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 {
        //創(chuàng)建結(jié)果對象
        Map<String, Object> res = new HashMap<String, Object>();
        res.put("code", "4000");
        res.put("message", "該賬號已從其他設(shè)備登錄");

        //返回響應(yīng)
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(event.getResponse());
        httpResponseConverter.write(res, null, httpResponse);
    }

}
5.2 httpSecurity
// 賬號只登陸一次
http.sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()));

SpringSecurity配置基本上好了。最后結(jié)構(gòu)和配置如下:


結(jié)構(gòu)

配置:

@Configuration
// mvc項目需要手動加這個注解,Springboot項目在我們引入security時自動加了
//@EnableWebSecurity
public class WebSecurityConfig {
    
    /**
     * 默認(rèn)密碼加密過時了,說不安全
     * 我們換一個安全的
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于內(nèi)存的用戶認(rèn)證
     * 注意 有自定義配置后,application.yml文件中配置的賬號密碼就失效了
     */
//    @Bean
//    UserDetailsService userDetailsService() {
//      // 內(nèi)存用戶管理器
//      InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//      manager.createUser(
//              // 創(chuàng)建UserDetails對象,管理賬號、密碼、角色、權(quán)限等
//              User.withUsername("user")
//                  .password(passwordEncoder().encode("password") )
//                  .roles("USER")
//                  .build());
//      
//      return manager;
//  }
    
    /**
     * 基于數(shù)據(jù)源的用戶認(rèn)證
     */
    @Bean
    UserDetailsService userDetailsService() {
        MyUserDetailService userDetail = new MyUserDetailService();
        return userDetail;
    }

    
    /**
     * HttpSecurity功能設(shè)置,根據(jù)功能,得到自己的過濾器鏈
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 權(quán)限設(shè)置
            .authorizeHttpRequests(authorize -> 
                authorize
                    // /test路徑 放行
                    .requestMatchers("/test/**").permitAll()
                    // /test1路徑需要test1權(quán)限
                    .requestMatchers("/test1/**").hasAuthority("test1")
                    // 其他任何資源請求
                    .anyRequest()
                    // 都需要登陸
                    .authenticated()
            )
            // 登錄頁、登出頁使用security提供的表單模式
//          .formLogin(Customizer.withDefaults())
            // 自定義登陸頁面
            .formLogin(form -> {
                form.loginPage("/tologin").permitAll()
                //自定義表單用戶名參數(shù),默認(rèn)是username
                .usernameParameter("username") 
                //自定義表單密碼參數(shù),默認(rèn)是password
                .passwordParameter("password") 
                //登錄失敗的返回地址,加了參數(shù),是因為有這個參數(shù),頁面會有提示
//              .failureUrl("/tologin?error")
                // 登陸成功處理
                .successHandler(new MyAuthenticationSuccessHandler())
                // 登錄失敗處理
                .failureHandler(new MyAuthenticationFailureHandler())
                ;
            })
            // 使用賬號密碼這種登陸方式,有formLogin這個可以不用
//          .httpBasic(Customizer.withDefaults())
            ;
        
        // 關(guān)閉post請求的 csrf
        http.csrf(csrf -> csrf.disable());
        
        // 注銷配置
        http.logout(logout -> logout.logoutSuccessHandler(new MyLogoutSuccessHandler()));
        
        // 錯誤處理
        // 請求未認(rèn)證的接口
        http.exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()));
        
        // 賬號只登陸一次
        http.sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()));
        
        return http.build();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容