前面說了用戶的配置,并沒有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();
}
}