Spring Security 最佳實踐
閱讀本文大概需要 10 分鐘。
來自:juejin.cn/post/7026734817853210661
Spring Security簡介 Spring Security 認證流程 Spring Security 項目搭建 導(dǎo)入依賴 訪問頁面 自定義用戶名和密碼 UserDetailsService詳解 PasswordEncoder密碼解析器詳解 登錄配置 角色權(quán)限 403 權(quán)限不足頁面處理 RememberMe(記住我) Spring Security 注解 Spring Security中CSRF 什么是CSRF?
Spring Security簡介
Spring Security 認證流程
SpringSecurity認證執(zhí)行流程Spring Security 項目搭建
導(dǎo)入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
訪問頁面
http://localhost:8080/ 進入Spring Security內(nèi)置登錄頁面自定義用戶名和密碼
# 靜態(tài)用戶,一般只在內(nèi)部網(wǎng)絡(luò)認證中使用,如:內(nèi)部服務(wù)器1,訪問服務(wù)器2
spring:
security:
user:
name: test # 通過配置文件,設(shè)置靜態(tài)用戶名
password: test # 配置文件,設(shè)置靜態(tài)登錄密碼
UserDetailsService詳解
@Component
public class UserSecurity implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userService.login(userName);
System.out.println(user);
if (null==user){
throw new UsernameNotFoundException("用戶名錯誤");
}
org.springframework.security.core.userdetails.User result =
new org.springframework.security.core.userdetails.User(
userName,user.getPassword(), AuthorityUtils.createAuthorityList()
);
return result;
}
}
PasswordEncoder密碼解析器詳解
PasswordEncoder
自定義密碼解析器
編寫類,實現(xiàn)PasswordEncoder 接口
/**
* 憑證匹配器,用于做認證流程的憑證校驗使用的類型
* 其中有2個核心方法
* 1. encode - 把明文密碼,加密成密文密碼
* 2. matches - 校驗明文和密文是否匹配
* */
public class MyMD5PasswordEncoder implements PasswordEncoder {
/**
* 加密
* @param charSequence 明文字符串
* @return
*/
@Override
public String encode(CharSequence charSequence) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
return toHexString(digest.digest(charSequence.toString().getBytes()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
/**
* 密碼校驗
* @param charSequence 明文,頁面收集密碼
* @param s 密文 ,數(shù)據(jù)庫中存放密碼
* @return
*/
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(encode(charSequence));
}
/**
* @param tmp 轉(zhuǎn)16進制字節(jié)數(shù)組
* @return 飯回16進制字符串
*/
private String toHexString(byte [] tmp){
StringBuilder builder = new StringBuilder();
for (byte b :tmp){
String s = Integer.toHexString(b & 0xFF);
if (s.length()==1){
builder.append("0");
}
builder.append(s);
}
return builder.toString();
}
}
/**
* 加密
* @return 加密對象
* 如需使用自定義密碼憑證匹配器 返回自定義加密對象
* 例如: return new MD5PasswordEncoder();
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); //Spring Security 自帶
}
登錄配置
方式一 轉(zhuǎn)發(fā)
http.formLogin()
.usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認username
.passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認password
.loginPage("/toLogin") // 當用戶未登錄的時候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認 /login
.loginProcessingUrl("/login") // 用戶登錄邏輯請求地址是什么。 默認是 /login
.failureForwardUrl("/failure"); // 登錄失敗后,請求轉(zhuǎn)發(fā)的位置。Security請求轉(zhuǎn)發(fā)使用Post請求。默認轉(zhuǎn)發(fā)到:loginPage?error
.successForwardUrl("/toMain"); // 用戶登錄成功后,請求轉(zhuǎn)發(fā)到的位置。Security請求轉(zhuǎn)發(fā)使用POST請求。
方式二 :重定向
http.formLogin()
.usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認username
.passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認password
.loginPage("/toLogin") // 當用戶未登錄的時候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認 /login
.loginProcessingUrl("/login") // 用戶登錄邏輯請求地址是什么。 默認是 /login
.defaultSuccessUrl("/toMain",true); //用戶登錄成功后,響應(yīng)重定向到的位置。GET請求。必須配置絕對地址。
.failureUrl("/failure"); // 登錄失敗后,重定向的位置。
方式三:自定義登錄處理器
/*自定義登錄失敗處理器*/
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
private boolean isRedirect;
public MyAuthenticationFailureHandler(String url, boolean isRedirect) {
this.url = url;
this.isRedirect = isRedirect;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
if (isRedirect){
httpServletResponse.sendRedirect(url);
}else {
httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);
}
}
//get set 方法 省略
自定義登錄成功邏輯處理器
/**
* 自定義登錄成功后處理器
* 轉(zhuǎn)發(fā)重定向,有代碼邏輯實現(xiàn)
* */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
private boolean isRedirect;
public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {
this.url = url;
this.isRedirect = isRedirect;
}
/**
* @param request 請求對象 request.getRequestDispatcher.forward()
* @param response 響應(yīng)對象 response.sendRedirect()
* @param authentication 用戶認證成功后的對象。其中報換用戶名權(quán)限結(jié)合,內(nèi)容是
* 自定義UserDetailsService
* */
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (isRedirect){
response.sendRedirect(url);
}else {
request.getRequestDispatcher(url).forward(request,response);
}
}
//get set 方法 省略
http.formLogin()
.usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認username
.passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認password
.loginPage("/toLogin") // 當用戶未登錄的時候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認 /login
.loginProcessingUrl("/login") // 用戶登錄邏輯請求地址是什么。 默認是 /login
登錄相關(guān)配置類
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserSecurity userSecurity;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
/**
* 加密
* @return 加密對象
* 如需使用自定義加密邏輯 返回自定義加密對象
* return new MD5PasswordEncoder(); return new SimplePasswordEncoder();
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); //Spring Security 自帶
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置登錄請求相關(guān)內(nèi)容。
http.formLogin()
.loginPage("/toLogin") // 當用戶未登錄的時候,跳轉(zhuǎn)的登錄頁面地址是什么? 默認 /login
.usernameParameter("name") // 設(shè)置請求參數(shù)中,用戶名參數(shù)名稱。 默認username
.passwordParameter("pswd") // 設(shè)置請求參數(shù)中,密碼參數(shù)名稱。 默認password
.loginProcessingUrl("/login") //設(shè)置登錄 提交表單數(shù)據(jù)訪問請求地址
.defaultSuccessUrl("/toMain")
.failureUrl("/toLogin");
//.successForwardUrl("/toMain")
//.failureForwardUrl("/toLogin");
//.successHandler(new LoginSuccessHandler("/toMain", true)) //自定義登錄成功處理器
//.failureHandler(new LoginErrorHandler("/toLogin", true));
http.authorizeRequests()
//.antMatchers("/toLogin").anonymous() //只能匿名用戶訪問
.antMatchers("/toLogin", "/register", "/login", "/favicon.ico").permitAll() // /toLogin請求地址,可以隨便訪問。
.antMatchers("/**/*.js").permitAll() // 授予所有目錄下的所有.js文件可訪問權(quán)限
.regexMatchers(".*[.]css").permitAll() // 授予所有目錄下的所有.css文件可訪問權(quán)限
.anyRequest().authenticated(); // 任意的請求,都必須認證后才能訪問。
// 配置退出登錄
http.logout()
.invalidateHttpSession(true) // 回收HttpSession對象。退出之前調(diào)用HttpSession.invalidate() 默認 true
.clearAuthentication(true) // 退出之前,清空Security記錄的用戶登錄標記。 默認 true
// .addLogoutHandler() // 增加退出處理器。
.logoutSuccessUrl("/") // 配置退出后,進入的請求地址。 默認是loginPage?logout
.logoutUrl("/logout"); // 配置退出登錄的路徑地址。和頁面請求地址一致即可。
// 關(guān)閉CSRF安全協(xié)議。
// 關(guān)閉是為了保證完整流程的可用。
http.csrf().disable();
}
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
角色權(quán)限
? 「hasAuthority(String)」 判斷角色是否具有特定權(quán)限 ?
http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")
? 「hasAnyAuthority(String ...)」 如果用戶具備給定權(quán)限中某一個,就允許訪問 ?
http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx")
? 「hasRole(String)」 如果用戶具備給定角色就允許訪問。否則出現(xiàn)403 ?
//請求地址為/admin/read的請求,必須登錄用戶擁有'管理員'角色才可訪問
http.authorizeRequests().antMatchers("/admin/read").hasRole("管理員")
? 「hasAnyRole(String ...)」 如果用戶具備給定角色的任意一個,就允許被訪問 ?
//用戶擁有角色是管理員 或 訪客 可以訪問 /guest/read
http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理員", "訪客")
? 「hasIpAddress(String)」 請求是指定的IP就運行訪問 ?
//ip 是127.0.0.1 的請求 可以訪問/ip
http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")
403 權(quán)限不足頁面處理
/**
* @describe 403 權(quán)限不足
* @author: AnyWhere
* @date 2021/4/18 20:57
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(
"<html>" +
"<body>" +
"<div style='width:800px;text-align:center;margin:auto;font-size:24px'>" +
"權(quán)限不足,請聯(lián)系管理員" +
"</div>" +
"</body>" +
"</html>"
);
response.getWriter().flush();//刷新緩沖區(qū)
}
}
// 配置403訪問錯誤處理器。
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/
RememberMe(記住我)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置記住密碼
http.rememberMe()
.rememberMeParameter("remember-me") // 修改請求參數(shù)名。 默認是remember-me
.tokenValiditySeconds(14*24*60*60) // 設(shè)置記住我有效時間。單位是秒。默認是14天
.rememberMeCookieName("remember-me") // 修改remember me的cookie名稱。默認是remember-me
.tokenRepository(persistentTokenRepository) // 配置用戶登錄標記的持久化工具對象。
.userDetailsService(userSecurity); // 配置自定義的UserDetailsService接口實現(xiàn)類對象
}
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
Spring Security 注解
@Secured
? 角色校驗 ,請求到來訪問控制單元方法時必須包含XX角色才能訪問 角色必須添加ROLE_前綴 ?
@Secured({"ROLE_管理員","ROLE_訪客"})
@RequestMapping("/toMain")
public String toMain(){
return "main";
}
@EnableGlobalMethodSecurity(securedEnabled = true)
@PreAuthorize
? 權(quán)限檢驗,請求到來訪問控制單元之前必須包含xx權(quán)限才能訪問,控制單元方法執(zhí)行前進行角色校驗 ?
/**
* [ROLE_管理員, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
* @PreAuthorize 角色 、權(quán)限 校驗 方法執(zhí)行前進行角色校驗
*
* hasAnyAuthority()
* hasAuthority()
*
* hasPermission()
*
*
* hasRole()
* hasAnyRole()
* */
@PreAuthorize("hasAnyRole('ROLE_管理員','ROLE_訪客')")
@RequestMapping("/toMain")
@PreAuthorize("hasAuthority('admin:write')")
public String toMain(){
return "main";
}
@PreAuthorize和@PostAuthorize 需要在配置類中配置注解@EnableGlobalMethodSecurity 才能生效@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize
? 權(quán)限檢驗,請求到來訪問控制單元之后必須包含xx權(quán)限才能訪問 ,控制單元方法執(zhí)行完后進行角色校驗 ?
/**
* [ROLE_管理員, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
* @PostAuthorize 角色 、權(quán)限 校驗 方法執(zhí)行后進行角色校驗
*
* hasAnyAuthority()
* hasAuthority()
* hasPermission()
* hasRole()
* hasAnyRole()
* */
@PostAuthorize("hasRole('ROLE_管理員')")
@RequestMapping("/toMain")
@PreAuthorize("hasAuthority('admin:write')")
public String toMain(){
return "main";
}
Spring Security 整合Thymeleaf 進行權(quán)限校驗
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
Spring Security中CSRF
什么是CSRF?
互聯(lián)網(wǎng)初中高級大廠面試題(9個G) 內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!
?戳閱讀原文領(lǐng)?。?/span> 朕已閱


