建立專案
先建立一個spring專案。
然後編寫pom檔案如下,引入spring-boot-starter-security,我這裡使用的spring boot是2.4.2,這裡使用使用spring-boot-dependencies,在這裡就能找到對應的security的包。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>app-kiba-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>app-kiba-security</name>
<description>app-kiba-security</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.kiba.appkibasecurity.AppKibaSecurityApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然後訪問建立專案時預設生成的介面:http://127.0.0.1:8080/user/123/roles/222,得到如下介面。
這是相當於,在我們的介面請求的前面做了一個攔截,類似filter,攔截後,跳轉到了一個介面,讓我們輸入賬號密碼。這裡,我由於沒有設定賬號密碼,所以登入不進去。
設定訪問一
下面設定一個賬號密碼,並且設定hello介面可以直接訪問,設定很簡單,就是注入兩個bean,InMemoryUserDetailsManager和WebSecurityCustomizer,程式碼如下:
@Configuration
public class SecurityConfig {
/**
* 註冊使用者,這裡使用者是在記憶體中的
* {noop}表示“無操作”(No Operation)密碼編碼。
* @return
*/
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("kiba").password("{noop}123").roles("admin").build());
return users;
}
/**
* 讓hello可以不用登入,就可以直接訪問,例如:http://127.0.0.1:8080/hello?name=kiba就可以直接訪問
* @return
*/
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return new WebSecurityCustomizer() {
@Override
public void customize(WebSecurity web) {
web.ignoring().antMatchers("/hello");
}
};
}
}
現在我們訪問http://127.0.0.1:8080/user/123/roles/222,進入到登入頁面,輸入kiba/123就可以檢視介面執行的結果了。
http://127.0.0.1:8080/hello?name=kiba就無需登入,可以直接訪問。
登入一次,其他介面就可以自由訪問了
控制請求
現在,增加一個類SecurityAdapter,繼承自WebSecurityConfigurerAdapter。然後重寫他的configure方法
@Configuration
@AllArgsConstructor
public class SecurityAdapter extends WebSecurityConfigurerAdapter {
/**
* authenticated():使用者需要透過使用者名稱/密碼登入,記住我功能也可以(remember-me)。
* fullyAuthenticated()使用者需要透過使用者名稱/密碼登入,記住我功能不行。
*/
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
http.httpBasic().and()
//禁用跨站請求偽造(CSRF)保護。
.csrf().disable()
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
當使用,增加了SecurityAdapter後,我們重新請求http://127.0.0.1:8080/user/123/roles/222,得到介面如下:
可以看到,登入介面的樣式被美化了。
設定訪問二(推薦)
我們還可以使用第二種方法,來做使用者密碼的配置。
透過重寫configure(AuthenticationManagerBuilder auth)函式,來建立使用者,這種方式建立使用者會將前面的bean-UserDetailsService給覆蓋,即,使用者只剩下這裡建立的。
程式碼如下:
@Configuration
@AllArgsConstructor
public class SecurityAdapter extends WebSecurityConfigurerAdapter {
/**
* authenticated():使用者需要透過使用者名稱/密碼登入,記住我功能也可以(remember-me)。
* fullyAuthenticated()使用者需要透過使用者名稱/密碼登入,記住我功能不行。
*/
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
http.httpBasic().and()
//禁用跨站請求偽造(CSRF)保護。
.csrf().disable()
.authorizeRequests().anyRequest().fullyAuthenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("kiba518")
.password(passwordEncoder().encode("123"))
.authorities(new ArrayList<>(0));
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
這裡的使用者是寫死的,使用者是可以修改成讀取資料庫的資訊的。
我們檢視WebSecurityConfigurerAdapter的程式碼,可以看到他有註解@Order(100),數越大,執行越優先順序越低,即,他的執行順序是相對比較靠後的。
授權OAuth2
授權這個設計理念是這樣,它是結合上面的security的操作,實現了一個普通的WebApp轉換成授權伺服器WebApp。
授權伺服器轉換思路
我們先了解一下security轉授權伺服器的思路。
1,在這個應用裡,建立一個auth介面,然後任何人想訪問這個介面,就都需要輸入賬戶密碼了。
2,我們這個auth介面的返回值是個code,然後我們的前端,或者其他呼叫介面的APP,就可以把這個code作為使用者登入的token了,。
3,然後我們再做一個介面,接受一個token引數,可以驗證token是否有效。
這樣我們這個授權伺服器的搭建思路就構建完成了。
但按這個思路,我們需要做很多操作,比如建立介面,快取token等等,現在spring提供了一個Oauth2的包,他可以幫我們實現這些介面定義。
OAuth2的介面如下,可以自行研究。
/oauth/authorize:授權端點
/oauth/token:獲取令牌端點
/oauth/confirm_access:使用者確認授權提交端點
/oauth/error:授權服務錯誤資訊端點
/oauth/check_token:用於資源服務訪問的令牌解析端點
/oauth/token_key:提供公有密匙的端點,如果使用JWT令牌的話
實現授權伺服器
現在我們實現一個授權伺服器。
先新增OAuth2的引用。
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
然後增加配置檔案AuthorizationConfig。
@Configuration
@EnableAuthorizationServer //開啟授權服務
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允許表單提交
security.allowFormAuthenticationForClients()
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-kiba") //客戶端唯一標識(client_id)
.secret(passwordEncoder.encode("kiba518-123456")) //客戶端的密碼(client_secret),這裡的密碼應該是加密後的
.authorizedGrantTypes("password") //授權模式標識,共4種模式[授權碼(authorization-code)隱藏式(implicit) 密碼式(password)客戶端憑證(client credentials)]
.scopes("read_scope"); //作用域
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
然後開啟SecurityAdapter,增加一個bean,如下,目的是讓上面的AuthorizationConfig裡Autowired的authenticationManager可以例項化。
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
然後使用APIFox呼叫一下/oauth/token介面。
先選擇auth,輸入賬號密碼,這個賬號密碼就是AuthorizationConfig裡配置的客戶端id和密碼。
這個資料在請求時,會進行base64編碼,然後以http的header屬性Authorization的值的模式傳遞,如下。
然後輸入引數,引數裡scope和grant_type要和AuthorizationConfig裡定義的scopes和authorizedGrantTypes一樣,如下。
請求後,得到結果,如上圖。
我們得到"access_token": "19d37af2-6e13-49c3-bf19-30a738b56886"。
有了access_token後,我們的前端其實就已經可以進行各種騷操作了。
資源服務
這個是Oauth為我們提供的一項很好用的功能。
我們建立一個專案做為資源服務。
新增依賴,版本與上面相同。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
然後編寫資源配置,程式碼如下:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Bean
public RemoteTokenServices remoteTokenServices() {
final RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("client-kiba");
tokenServices.setClientSecret("kiba518-123456");
tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");//這個介面是oauth自帶的
return tokenServices;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//session建立策略
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//所有請求需要認證
http.authorizeRequests().anyRequest().authenticated();
}
}
因為新增了spring-boot-starter-security,所以,我們請求這個資源WebApp,就都需要輸入賬號密碼。
但因為,我們配置了ResourceServerConfig,這裡我們配置了遠端token服務,設定的資訊是我們上面建立授權服務的資訊。所以,在訪問這個WebApp時,我們提供token即可。
使用APIFOX測試,先新增auth的token,內容是來自於上面,/oauth/token的返回值access_token的值。
然後請求user介面,我這user介面沒有引數,請求結果如下:
總結
這個授權服務挺好用的,就是配置太繁瑣了,初學者不太好理解,而且功能太多,配置太鬧心。
這個資源服務還是很貼心的,他提我們實現了,tokencheck的部分,但要注意的是,他這tokencheck是基於http請求的。
雖然Oath很好用,但,我還是覺得,這個認證部分自己寫比較好,我們可以根據專案的需求,設計輕量級的授權認證。
比如,我們想減少http請求,把部分tokencheck在快取內進行check,那使用oauth時,修改起來就會很頭疼。如果是自己寫的授權伺服器,就不會有修改困難的問題。
注:此文章為原創,任何形式的轉載都請聯絡作者獲得授權並註明出處!
若您覺得這篇文章還不錯,請點選下方的【推薦】,非常感謝!
https://www.cnblogs.com/kiba/p/18252859