分布式oauth2单点登录 🗺️

  • 1. 创建 Authorization Server
  • 1.1 添加依赖
  • 1.2 创建用户实体类
  • 1.3 创建 UserRepository 接口
  • 1.4 创建 UserDetailsService 实现类
  • 1.5 创建 WebSecurityConfig 配置类
  • 1.6 创建 LoginController
  • 1.7 创建登录页面
  • 1.8 配置授权服务器
  • 2. 创建 Resource Server
  • 2.1 添加依赖
  • 2.2 创建 Resource Server 配置类
  • 2.3 创建受保护资源的 API
  • 2.4 创建主应用类
  • 2.5 配置应用端口和上下文路径(可选)
  • 3. 创建 Client 应用
  • 3.1 添加依赖
  • 3.2 创建 SecurityConfig 配置类
  • 3.3 配置 application.yml 文件
  • 3.4 创建主应用类
  • 3.5 创建 HomeController 控制器
  • 3.6 创建主页模板
  • 3.7 创建 ResourceController 控制器
  • 3.8 创建受保护资源页面模板


原来,成全别人,是要有一点痛苦的

为了使用 Spring Cloud、Spring Cloud Security 和 OAuth2 实现单点登录,你需要完成以下步骤:

  1. 创建 Authorization Server
  2. 创建 Resource Server
  3. 创建 Client 应用

1. 创建 Authorization Server

好的,让我们详细介绍如何创建认证服务器,并实现登录页面。

1.1 添加依赖

首先,在你的 Maven pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

1.2 创建用户实体类

在认证服务器中,创建一个用户实体类 User。该实体类将表示用户在数据库中的信息。

@Entity
@Table(name = "users")
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String username;
    private String password;
    private boolean enabled;

    // 省略 getter 和 setter

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
}

1.3 创建 UserRepository 接口

创建一个 UserRepository 接口,该接口继承自 JpaRepository。我们将使用此接口查询数据库中的用户信息。

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

1.4 创建 UserDetailsService 实现类

接下来,创建一个名为 UserDetailsServiceImpl 的类,该类实现了 UserDetailsService 接口。我们将使用这个类加载数据库中的用户信息。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return user;
    }
}

1.5 创建 WebSecurityConfig 配置类

创建一个名为 WebSecurityConfig 的配置类,该类继承自 WebSecurityConfigurerAdapter。我们将在这个类中配置安全相关的设置。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

   
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
    .authorizeRequests()
    .antMatchers("/login", "/css/", "/js/", "/images/**").permitAll()
    .anyRequest().authenticated()
    .and()
    .formLogin()
    .loginPage("/login")
    .permitAll()
    .and()
    .logout()
    .permitAll();
    }

    @Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}
}

1.6 创建 LoginController

现在,我们将创建一个名为 LoginController 的控制器类,用于处理登录页面的请求。

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

1.7 创建登录页面

src/main/resources/templates 目录下创建一个名为 login.html 的登录页面。这个页面将包含一个表单,用于输入用户名和密码。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>登录</title>
    <link rel="stylesheet" th:href="@{/css/login.css}" />
</head>
<body>
<div class="login-container">
    <h2>登录</h2>
    <form th:action="@{/login}" method="post">
        <div class="form-group">
            <label for="username">用户名</label>
            <input type="text" id="username" name="username" class="form-control" required autofocus />
        </div>
        <div class="form-group">
            <label for="password">密码</label>
            <input type="password" id="password" name="password" class="form-control" required />
        </div>
        <button type="submit" class="btn btn-primary">登录</button>
    </form>
</div>
</body>
</html>

1.8 配置授权服务器

现在,我们需要将认证服务器配置为 OAuth2 授权服务器。在上面创建的 AuthorizationServerConfig 类中,确保已经添加了以下内容:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource)
            .withClient("client-id")
            .secret(passwordEncoder().encode("client-secret"))
            .authorizedGrantTypes("authorization_code", "refresh_token")
            .scopes("read", "write")
            .autoApprove(true)
            .redirectUris("http://localhost:8082/login");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

现在,你已经成功创建了一个认证服务器,并实现了登录页面。启动应用后,访问 http://localhost:8080/login 即可看到登录页面。登录成功后,客户端应用将被授权访问受保护的资源。

2. 创建 Resource Server

接下来我们将详细介绍如何创建资源服务器。资源服务器将托管受保护的资源,并确保只有授权的客户端才能访问这些资源。

2.1 添加依赖

首先,在你的 Maven pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    </dependency>
</dependencies>

2.2 创建 Resource Server 配置类

接下来,创建一个名为 ResourceServerConfig 的配置类。我们将在这个类中配置资源服务器相关的设置。

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**").authenticated();
    }

}

2.3 创建受保护资源的 API

现在,我们将创建一个名为 ProtectedResourceController 的控制器类,用于处理受保护资源的请求。

@RestController
@RequestMapping("/api")
public class ProtectedResourceController {

    @GetMapping("/userinfo")
    public Map<String, Object> getUserInfo(@AuthenticationPrincipal OAuth2Authentication authentication) {
        return (Map<String, Object>) authentication.getUserAuthentication().getDetails();
    }

    @GetMapping("/hello")
    public String hello() {
        return "Hello, this is a protected resource.";
    }
}

2.4 创建主应用类

创建一个名为 ResourceServerApplication 的主应用类,用于启动资源服务器。

@SpringBootApplication
public class ResourceServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceServerApplication.class, args);
    }
}

2.5 配置应用端口和上下文路径(可选)

src/main/resources 目录下创建一个名为 application.yml 的配置文件。在这个文件中,我们可以设置应用的端口和上下文路径。这一步是可选的,如果你不进行配置,应用将使用默认端口 8080 和根上下文路径。

server:
  port: 8081
  servlet:
    context-path: /resource-server

现在,你已经创建了一个资源服务器,它包含一个受保护的 API。要启动应用,请运行 ResourceServerApplication 主类。启动后,资源服务器将监听在 http://localhost:8081/resource-server/api(或者在你自定义的端口和上下文路径)。

只有授权的客户端才能访问受保护的资源。客户端需要使用在认证服务器获取的访问令牌来访问这些资源。

3. 创建 Client 应用

客户端应用将使用 OAuth2 进行授权,并通过资源服务器访问受保护的资源。

3.1 添加依赖

首先,在你的 Maven pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    </dependency>
</dependencies>

3.2 创建 SecurityConfig 配置类

创建一个名为 SecurityConfig 的配置类,该类继承自 WebSecurityConfigurerAdapter。我们将在这个类中配置客户端应用的安全设置,以及 OAuth2 相关的配置。

@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/login**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"));
    }
}

3.3 配置 application.yml 文件

src/main/resources 目录下创建一个名为 application.yml 的配置文件。在这个文件中,我们将设置 OAuth2 客户端的相关配置。

security:
  oauth2:
    client:
      clientId: client-id
      clientSecret: client-secret
      accessTokenUri: http://localhost:8080/oauth/token
      userAuthorizationUri: http://localhost:8080/oauth/authorize
      scope:
        - read
        - write
    resource:
      userInfoUri: http://localhost:8081/api/userinfo

确保使用你在认证服务器上注册的 clientIdclientSecret,并将 accessTokenUriuserAuthorizationUri 设置为认证服务器的相应端点。userInfoUri 应该指向资源服务器上的受保护资源。

3.4 创建主应用类

创建一个名为 ClientApplication 的主应用类,用于启动客户端应用。

@SpringBootApplication
public class ClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class, args);
    }
}

3.5 创建 HomeController 控制器

创建一个名为 HomeController 的控制器类,用于处理客户端应用的主页请求。

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model, OAuth2AuthenticationToken authentication) {
        model.addAttribute("userName", authentication.getPrincipal().getAttribute("name"));
        return "home";
    }
}

3.6 创建主页模板

src/main/resources/templates 目录下创建一个名为 home.html 的主页模板。这个页面将显示登录用户的名字和一个链接,用于访问受保护的资源。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>主页</title>
    <link rel="stylesheet" th:href="@{/css/home.css}" />
</head>
<body>
<div class="home-container">
    <h2>Welcome, <span th:text="${userName}">User</span>!</h2>
    <p>
        <a href="/protected-resource">访问受保护资源</a>
    </p>
</div>
</body>
</html>

3.7 创建 ResourceController 控制器

创建一个名为 ResourceController 的控制器类,用于访问资源服务器上的受保护资源。这个类将使用 OAuth2RestTemplate 来携带访问令牌并发起请求。

@Controller
public class ResourceController {

    @Autowired
    private OAuth2RestTemplate restTemplate;

    @GetMapping("/protected-resource")
    public String getProtectedResource(Model model) {
        ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8081/resource-server/api/hello", String.class);
        model.addAttribute("resource", response.getBody());
        return "protectedResource";
    }
}

3.8 创建受保护资源页面模板

src/main/resources/templates 目录下创建一个名为 protectedResource.html 的受保护资源页面模板。这个页面将显示从资源服务器获取的受保护资源。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>受保护资源</title>
    <link rel="stylesheet" th:href="@{/css/protectedResource.css}" />
</head>
<body>
<div class="protected-resource-container">
    <h2>受保护资源</h2>
    <p th:text="${resource}"></p>
    <p>
        <a href="/">返回主页</a>
    </p>
</div>
</body>
</html>

现在,你已经创建了一个客户端应用。要启动应用,请运行 ClientApplication 主类。启动后,访问 http://localhost:8080,然后点击登录。登录成功后,你将被重定向回客户端应用的主页。点击链接以访问受保护的资源。