SpringBoot19 集成SpringSecurity01 -> 環境搭建、SpringSecurity驗證
1 環境搭建
1.1 創建一個SpringBoot項目
項目腳手架 -> 點擊前往
1.2 創建一個Restful接口
新建一個Controller類即可
package com.example.wiremock.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; importSecurityController.javaorg.springframework.web.bind.annotation.RestController; /** * @author 王楊帥 * @create 2018-05-12 21:18 * @desc **/ @RestController @RequestMapping(value = "/security") @Slf4j public class SecurityController { @GetMapping(value = "/connect") public String connect() { String result = "前後臺連接成功"; log.info("===" + result); return result; } }
1.3 引入SpringSecurity相關jar包
<!--security相關--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
1.4 啟動項目
技巧01:由於我們引入了SpringSecurity相關的jar包,所以系統會默認開啟SpringSecurity的相關配置
技巧02:可以在配置文件中關掉這個配置(即:使SpringSecurity失效)
技巧03:啟動項目後會在控制臺上打印出一個密碼,因為默認的SpringSecurity配置會對所有的請求都進行權限驗證,如果不通過就會跳轉到 /login 請求,則是一個登陸頁面或者一個登陸彈出窗口,默認登陸名為 user,默認登陸密碼就是啟動項目是控制臺打印出來的字符串
1.5 訪問接口
IP + 端口 + 上下文p徑 + 請求路徑
http://127.0.0.1:9999/dev/security/connect
技巧01:SpringSecurity默認的配置會默認對所有的請求都進行權限驗證,所以會跳轉到 /login 請求路徑,畫面如下;輸入正確的用戶名和密碼後跳轉到之的請求所得到的響應
1.6 SpringSecurity的授權流程
所有請求url -> BasicAuthenticationFilter / UsernamePasswordAuthenticationFilter -> FilterSecurityInterceptor -> BasicAuthenticationFilter / UsernamePasswordAuthenticationFilter -> FilterSecurityInterceptor -> controller層
所有請求都默認進入 BasicAuthenticationFilter 過濾器進行過濾,然後進入 FilterSecurityInterceptor 過濾器進行權限驗證,如果在 FilterSecurityInterceptor 中權限驗證就會跳轉到 /login 請求進行處理,然後在進入 BasicAuthenticationFilter 或者 UsernamePasswordAuthenticationFilter 過濾器,再進入 FilterSecurityInterceptor,只有當 FilterSecurityInterceptor 過濾通過了才會跳轉到之前的請求路徑
技巧01:如果在 FilterSecurityInterceptor 中拋出了異常就會跳轉到 ExceptionTranslationFilter 進行相應的處理
2 SpringSecurity驗證
直接使用SpringSecurity默認的配置進行權限驗證時只有一個用戶,無法滿足實際開發需求;在實際的開發中需要根據不同的用戶判斷其權限
技巧01:直接繼承一個UserDetailsService接口即可;該接口中有一個 loadUserByUsername 方法,該方法是通過用戶名查找用戶信息,然後在根據查到的用戶信息來判斷該用戶的權限
2.1 實現 UserDetailsService
技巧01:實現了 UserDetailsService接口的實現類必須在類級別上添加@Bean註解,目的上讓Spring容器去管理這個Bean
技巧02:可以在實現了 UserDetailsService接口的實現類中依賴註入其他Bean(例如:依賴註入持久層Bean來實現數據庫操作)
技巧03:如果實現了 UserDetailsService 接口就必須進行 SpringSecurity 配置,因為SpringSecurity會使用一個實現了PasswordEncoder接口的實現類去比較用戶錄入的密碼和從數據庫中獲取到的密碼是否相等
package com.example.wiremock.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; //import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * @author 王楊帥 * @create 2018-05-12 22:09 * @desc **/ @Component @Slf4j public class FuryUserDetailService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; // 01 依賴註入持久層(用於查找用戶信息) @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("用戶名:" + username); // 技巧01: /login 請求傳過來的用戶名會傳到這裏 // 02 根據用戶名到數據庫中查找數據 String pwd = passwordEncoder.encode("123321"); // 技巧02:此處是模擬的從數據庫中查詢到的密碼 // 03 返回一個 User 對象(技巧01:這個User對象時實現了UserDetail接口的,這裏利用的是Spring框架提供的User對象,也可以使用自定義但是實現了UserDetai接口的User對象) return new User(username, pwd, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }FuryUserDetailService
2.2 SpringSecurity配置
技巧01:其實就是配置一個Bean而已,只不過這個Bean的返回類型是 PasswordEncoder 類型
技巧02:可以使用實現了 PasswordEncoder接口的實現類 BCryptPasswordEncoder 作為返回類型,也可以使用自定義並且實現了 PasswordEncoder接口的類作為返回類型
坑01:WebSecurityConfigurerAdapter 這個抽象類可能已經過時,解決辦法 -> 點擊前往
package com.example.wiremock.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // return new MyPasswordEncoder(); } }BrowserSecurityConfig.java
2.3 自定義加密類
就是一個實現了 PasswordEncoder接口的類而已,我們可以通過該類來實現MD5加密或者一些其他的加密方式
2.3.2 加密類
用於實現自己的加密算法
package com.example.wiremock.config; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author 王楊帥 * @create 2018-05-12 22:41 * @desc **/ public class MyPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { if (charSequence.toString().equals(s)) { return true; } return false; } }MyPasswordEncoder.java
2.3.3 重新進行SrpingSecurity配置
package com.example.wiremock.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); return new MyPasswordEncoder(); } }BrowserSecurityConfig.java
2.4 測試
啟動項目後進入到登錄頁面
技巧01:隨便輸入一個用戶名(PS:由於後臺沒有實現根據用戶名查詢用戶信息的邏輯,若依隨便輸入一個即可),輸入一個固定的密碼(PS:這個密碼要和loadUserByUsername方法中返回的User對象中的password參數加密前的內容一致)
2.5 進階
loadUserByUsername 方法的返回類型是一個User對象,這個User對象有兩個默認的構造器,一個僅僅包含用戶名、用戶秘密和權限,另一個除了包含這些信息還包含一些用戶的有效性信息
技巧01:直接看 UserDetails 就知道了
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.util.Assert; public class User implements UserDetails, CredentialsContainer { private static final long serialVersionUID = 500L; private static final Log logger = LogFactory.getLog(User.class); private String password; private final String username; private final Set<GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled; public User(String username, String password, Collection<? extends GrantedAuthority> authorities) { this(username, password, true, true, true, true, authorities); } public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { if (username != null && !"".equals(username) && password != null) { this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); } else { throw new IllegalArgumentException("Cannot pass null or empty values to constructor"); } } public Collection<GrantedAuthority> getAuthorities() { return this.authorities; } public String getPassword() { return this.password; } public String getUsername() { return this.username; } public boolean isEnabled() { return this.enabled; } public boolean isAccountNonExpired() { return this.accountNonExpired; } public boolean isAccountNonLocked() { return this.accountNonLocked; } public boolean isCredentialsNonExpired() { return this.credentialsNonExpired; } public void eraseCredentials() { this.password = null; } private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) { Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection"); SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet(new User.AuthorityComparator()); Iterator var2 = authorities.iterator(); while(var2.hasNext()) { GrantedAuthority grantedAuthority = (GrantedAuthority)var2.next(); Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements"); sortedAuthorities.add(grantedAuthority); } return sortedAuthorities; } public boolean equals(Object rhs) { return rhs instanceof User ? this.username.equals(((User)rhs).username) : false; } public int hashCode() { return this.username.hashCode(); } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()).append(": "); sb.append("Username: ").append(this.username).append("; "); sb.append("Password: [PROTECTED]; "); sb.append("Enabled: ").append(this.enabled).append("; "); sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; "); sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired).append("; "); sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; "); if (!this.authorities.isEmpty()) { sb.append("Granted Authorities: "); boolean first = true; Iterator var3 = this.authorities.iterator(); while(var3.hasNext()) { GrantedAuthority auth = (GrantedAuthority)var3.next(); if (!first) { sb.append(","); } first = false; sb.append(auth); } } else { sb.append("Not granted any authorities"); } return sb.toString(); } public static User.UserBuilder withUsername(String username) { return builder().username(username); } public static User.UserBuilder builder() { return new User.UserBuilder(); } /** @deprecated */ @Deprecated public static User.UserBuilder withDefaultPasswordEncoder() { logger.warn("User.withDefaultPasswordEncoder() is considered unsafe for production and is only intended for sample applications."); PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); User.UserBuilder var10000 = builder(); encoder.getClass(); return var10000.passwordEncoder(encoder::encode); } public static User.UserBuilder withUserDetails(UserDetails userDetails) { return withUsername(userDetails.getUsername()).password(userDetails.getPassword()).accountExpired(!userDetails.isAccountNonExpired()).accountLocked(!userDetails.isAccountNonLocked()).authorities(userDetails.getAuthorities()).credentialsExpired(!userDetails.isCredentialsNonExpired()).disabled(!userDetails.isEnabled()); } public static class UserBuilder { private String username; private String password; private List<GrantedAuthority> authorities; private boolean accountExpired; private boolean accountLocked; private boolean credentialsExpired; private boolean disabled; private Function<String, String> passwordEncoder; private UserBuilder() { this.passwordEncoder = (password) -> { return password; }; } public User.UserBuilder username(String username) { Assert.notNull(username, "username cannot be null"); this.username = username; return this; } public User.UserBuilder password(String password) { Assert.notNull(password, "password cannot be null"); this.password = password; return this; } public User.UserBuilder passwordEncoder(Function<String, String> encoder) { Assert.notNull(encoder, "encoder cannot be null"); this.passwordEncoder = encoder; return this; } public User.UserBuilder roles(String... roles) { List<GrantedAuthority> authorities = new ArrayList(roles.length); String[] var3 = roles; int var4 = roles.length; for(int var5 = 0; var5 < var4; ++var5) { String role = var3[var5]; Assert.isTrue(!role.startsWith("ROLE_"), role + " cannot start with ROLE_ (it is automatically added)"); authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } return this.authorities((Collection)authorities); } public User.UserBuilder authorities(GrantedAuthority... authorities) { return this.authorities((Collection)Arrays.asList(authorities)); } public User.UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) { this.authorities = new ArrayList(authorities); return this; } public User.UserBuilder authorities(String... authorities) { return this.authorities((Collection)AuthorityUtils.createAuthorityList(authorities)); } public User.UserBuilder accountExpired(boolean accountExpired) { this.accountExpired = accountExpired; return this; } public User.UserBuilder accountLocked(boolean accountLocked) { this.accountLocked = accountLocked; return this; } public User.UserBuilder credentialsExpired(boolean credentialsExpired) { this.credentialsExpired = credentialsExpired; return this; } public User.UserBuilder disabled(boolean disabled) { this.disabled = disabled; return this; } public UserDetails build() { String encodedPassword = (String)this.passwordEncoder.apply(this.password); return new User(this.username, encodedPassword, !this.disabled, !this.accountExpired, !this.credentialsExpired, !this.accountLocked, this.authorities); } } private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable { private static final long serialVersionUID = 500L; private AuthorityComparator() { } public int compare(GrantedAuthority g1, GrantedAuthority g2) { if (g2.getAuthority() == null) { return -1; } else { return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority()); } } } }User.java
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }UserDetails.java
待更新......
SpringBoot19 集成SpringSecurity01 -> 環境搭建、SpringSecurity驗證