Spring Boot/Angular整合Keycloak实现单点登录功能

下面是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. 等级管理系统

场景:一个等级管理系统,管理员可以添加、删除用户等级,只有等级为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();
  }
}
  1. 论坛系统

场景:一个论坛系统,只有登录用户才能发帖或回复,用户可以在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技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Java中进程与线程的区别

    Java中进程与线程的区别 在Java中,进程(Process)和线程(Thread)都是常见的概念。虽然它们的功能类似,但它们之间存在明显的不同。了解它们的区别对我们正确地设计和编写多线程程序非常重要。 进程和线程的定义 进程是操作系统操作的基本单位,它是程序执行时的一个实例。它拥有自己的内存空间、系统资源和进程上下文等。每个进程都有一个或多个线程,线程是…

    Java 2023年5月19日
    00
  • SpringMVC实现数据绑定及表单标签

    讲解“SpringMVC实现数据绑定及表单标签”的完整攻略如下: 1. 数据绑定 SpringMVC通过数据绑定将请求参数映射到控制器方法的入参中。实现数据绑定需要在控制器方法入参前面添加@ModelAttribute注解,例如: @RequestMapping(value="/user") public String showUserI…

    Java 2023年6月15日
    00
  • SpringBoot中的响应式web应用详解

    Spring Boot是一个用于构建基于Spring框架开发的应用程序的工具。其提供了快速的应用程序开发和易于使用的API,并确定了一些最佳实践,使得开发人员可以更加专注于应用程序功能和业务逻辑。而“响应式web应用”则是指使用非阻塞I/O的方式,能够更快地处理请求、响应更迅速和更多的请求、更少的资源消耗等特点。 搭建响应式web 应用,我们需要依赖于以下的…

    Java 2023年5月15日
    00
  • java+io+swing实现学生信息管理系统

    Java+IO+Swing实现学生信息管理系统 学生信息管理系统是一款常见的管理工具,它可以帮助学校、老师或管理员轻松地管理学生的信息。本篇攻略将会使用Java语言结合IO和Swing技术来实现学生信息管理系统。 1. 项目搭建 首先打开你喜欢的IDE,选择新建Java项目,并添加Swing库。 然后新建一个Main类,它将作为程序的入口点。接下来,创建一个…

    Java 2023年5月24日
    00
  • Java自动化工具Ant的基础使用教程

    Java自动化工具Ant的基础使用教程 简介 Ant(Another Neat Tool)是一个基于Java开发的构建工具,它是基于脚本的、可扩展的构建系统。Ant通过XML文件来进行构建,而无需使用特定的编程语言来编写构建逻辑。Ant可以自动编译Java代码,运行Junit测试,生成Java文档等。 基础使用 安装 下载Ant安装程序,官方下载地址为:ht…

    Java 2023年5月26日
    00
  • java事务的概念浅析

    接下来我将详细讲解“Java事务的概念浅析”的完整攻略。 Java事务的概念浅析 什么是事务 在计算机领域,事务是指一组对系统中数据的访问和更新操作,这组操作要么全都执行成功,要么全都不执行,保证了数据的一致性。事务是一种能够保证数据在一些列操作中的完整性和一致性的数据处理方式。 事务的ACID属性 在数据库中,事务必须同时具备ACID四个属性: 原子性(A…

    Java 2023年5月20日
    00
  • JAVA多线程知识汇总

    JAVA多线程知识汇总 为什么需要多线程 在单线程模式下,当代码执行到IO操作时,CPU资源就会空闲等待IO操作完成,这样会导致CPU效率低下。而多线程模式下,线程的数量可以与CPU的核心数相匹配,能够更好地充分利用CPU资源,在IO操作等待的同时处理其他代码而不会浪费CPU。 如何使用多线程 创建线程 Java中使用继承Thread类或者实现Runnabl…

    Java 2023年5月19日
    00
  • Java Date类的使用案例详解

    Java Date类的使用案例详解 简介 Java中的Date类用于表示日期和时间。它被广泛用于处理时间和日期相关的应用程序。Date类的对象表示一个特定的瞬间,它包含了自从标准基准时间(称为“历元”)以来的毫秒数。 使用步骤 要使用Date类,需要依次进行以下步骤: 创建Date对象 使用Date对象进行操作 创建Date对象 可以使用以下方式创建Date…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部