下面是Spring Boot/Angular整合Keycloak实现单点登录功能的完整攻略。
一、准备工作
1.安装并配置Java环境和Maven环境。
2.安装Keycloak,并创建相关的Realm和Client。
3.创建一个Angular项目,引入相关依赖。
二、配置Keycloak
1.打开Keycloak控制台,在Realm Setting中设置Valid Redirect URIs为http://localhost:4200/*
2.创建Client,设置Access Type为confidential,Valid Redirect URIs为http://localhost:4200/*
3.在Client Scope里添加一个Role,如:user,并分配给一个用户。
三、配置Spring Boot后端
1.引入相关依赖:
<dependencies>
<!-- Spring Security Web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- Spring Security OAuth2 Client -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<!-- Keycloak Adapter Spring Security -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>11.0.1</version>
</dependency>
</dependencies>
2.添加配置类
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/api/**").hasRole("user")
.anyRequest().permitAll();
http.csrf().disable();
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
// 持久化session
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
3.在配置文件中进行相关配置
server:
port: 8080
keycloak:
auth-server-url: http://localhost:8081/auth
realm: demo
resource: demo-backend
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: demo-backend
client-secret: 3094e29b-7450-4a9f-bfa5-1501e56fc8d7
access-token-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}/protocol/openid-connect/token
user-authorization-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}/protocol/openid-connect/auth
issuer-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}
scope: openid
provider:
keycloak:
issuer-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}
四、配置Angular前端
1.安装相关依赖
npm install --save angular-oauth2-oidc
npm install --save @auth0/angular-jwt
2.创建AuthService,实现登录、注销和Token验证的逻辑
@Injectable({
providedIn: 'root'
})
export class AuthService {
private readonly AUTH_SERVER = 'http://localhost:8081/auth/realms/demo';
private readonly LOGIN_URL = `${this.AUTH_SERVER}/protocol/openid-connect/auth`;
private readonly CLIENT_ID = 'demo-frontend';
private readonly RESPONSE_TYPE = 'code';
private readonly SCOPE = 'openid profile email';
private readonly REDIRECT_URI = 'http://localhost:4200/callback';
private readonly JWK_URI = `${this.AUTH_SERVER}/protocol/openid-connect/certs`;
private readonly TOKEN_URL = `${this.AUTH_SERVER}/protocol/openid-connect/token`;
private readonly LOGOUT_URL = `${this.AUTH_SERVER}/protocol/openid-connect/logout`;
private tokenSubject = new BehaviorSubject<Token>(null);
public token$ = this.tokenSubject.asObservable();
constructor(private readonly http: HttpClient,
private readonly oauthService: OAuthService,
private readonly jwtService: JwtHelperService) {
oauthService.configure({
issuer: this.AUTH_SERVER,
clientId: this.CLIENT_ID,
redirectUri: this.REDIRECT_URI,
scope: this.SCOPE,
responseType: this.RESPONSE_TYPE,
tokenEndpoint: this.TOKEN_URL,
userinfoEndpoint: `${this.AUTH_SERVER}/protocol/openid-connect/userinfo`,
jwksEndpoint: this.JWK_URI
});
oauthService.loadDiscoveryDocumentAndTryLogin();
oauthService.events.subscribe(
e => console.log(e)
);
}
public isAuthenticated(): boolean {
const jwt = this.oauthService.getAccessToken();
return !this.jwtService.isTokenExpired(jwt);
}
public async login(): Promise<void> {
this.oauthService.initLoginFlow();
await this.oauthService.tryLogin({});
}
public async logout(): Promise<void> {
await this.http.get(this.LOGOUT_URL).toPromise();
this.oauthService.logOut();
}
}
3.在AppComponent中使用AuthService
@Component({
selector: 'app-root',
template: `
<div *ngIf="authService.token$ | async as token; else loginTpl">
<p>用户{{ token.userName }}已登录</p>
<button (click)="logout()">注销</button>
<router-outlet></router-outlet>
</div>
<ng-template #loginTpl>
<button (click)="login()">登录</button>
</ng-template>`,
styles: []
})
export class AppComponent {
constructor(public readonly authService: AuthService) {
}
public login(): void {
this.authService.login();
}
public logout(): void {
this.authService.logout();
}
}
五、示例
这里提供两个示例:
- 等级管理系统
场景:一个等级管理系统,管理员可以添加、删除用户等级,只有等级为1的用户才能访问系统。
前端:等级管理页面,管理员登录后,在页面中展示当前系统所有的用户等级,提供添加、删除等操作的按钮。
后端:提供等级相关的api,get和post请求需登录后才能访问。
后端接口代码:
@RestController
public class RankController {
@GetMapping("/api/ranks")
public List<Integer> getRanks(HttpServletRequest request) {
return Arrays.asList(1, 2, 3);
}
@PostMapping("/api/ranks")
public void addRank(HttpServletRequest request, @RequestParam("rank") int rank) {
// 添加等级的逻辑
}
@DeleteMapping("/api/ranks")
public void deleteRank(HttpServletRequest request, @RequestParam("rank") int rank) {
// 删除等级的逻辑
}
}
在SecurityConfig中配置get和post请求需要登录:
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/api/**").hasRole("user")
.anyRequest().permitAll();
http.csrf().disable();
}
前端接口代码:
@Injectable({
providedIn: 'root'
})
export class RanksService {
private readonly URL = 'http://localhost:8080/api/ranks';
constructor(private readonly http: HttpClient) {
}
public getRanks(): Observable<number[]> {
return this.http.get<number[]>(this.URL);
}
public addRank(rank: number): void {
this.http.post(this.URL, {rank}).subscribe();
}
public deleteRank(rank: number): void {
const options = {params: new HttpParams().set('rank', rank)};
this.http.delete(this.URL, options).subscribe();
}
}
- 论坛系统
场景:一个论坛系统,只有登录用户才能发帖或回复,用户可以在header中看到自己的用户名。
前端:提供发帖、回复、帖子列表的页面,header中展示当前用户的用户名。
后端:提供帖子相关的api,get和post请求需登录后才能访问。
后端接口代码:
@RestController
public class PostController {
@GetMapping("/api/posts")
public List<String> getPosts(HttpServletRequest request) {
return Arrays.asList("Post 1", "Post 2", "Post 3");
}
@PostMapping("/api/posts")
public void addPost(HttpServletRequest request, @RequestBody Map<String, String> post) {
// 添加帖子的逻辑
}
}
在SecurityConfig中配置get和post请求需要登录:
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/api/**").hasRole("user")
.anyRequest().permitAll();
http.csrf().disable();
}
前端接口代码:
@Injectable({
providedIn: 'root'
})
export class PostsService {
private readonly URL = 'http://localhost:8080/api/posts';
constructor(private readonly http: HttpClient) {
}
public getPosts(): Observable<string[]> {
return this.http.get<string[]>(this.URL);
}
public addPost(post: string): void {
this.http.post(this.URL, {post}).subscribe();
}
}
六、总结
以上就是Spring Boot/Angular整合Keycloak实现单点登录功能的完整攻略,整合过程中的关键步骤和示例都在上述内容中进行了详细的说明。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Boot/Angular整合Keycloak实现单点登录功能 - Python技术站