三. 单点登录示例

一 . 概述

单点登录(Singal Sign On)是很多企业经常使用的一种登录方式,那么何为单点登录呢?为了解决什么样的问题呢?举个例子,在淘宝公司内部,有天猫、淘宝、阿里云、聚划算等众多的产品线,这些产品线是不同的服务器来支撑运行,但是作为用户的我们却可以使用同一套账户名和密码进行登录他几乎所有的产品,登录服务器只有一个,根据登录服务器返回的特定的信息,可以去访问它所有的产品线。着就是所谓的单点登录。

1.1 认证服务器

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <version>2.3.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.1.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
</dependencies>

Yml配置

server:
  port: 7070

  servlet:
    context-path: /auth

授权服务

@EnableAuthorizationServer
@Configuration
public class JwtServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    // jwt的生成器
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("abcxyz");
        return jwtAccessTokenConverter;
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.accessTokenConverter(jwtAccessTokenConverter())  //配置JWT的生成器
                 .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET);
    }
    // 客户端的配置
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()  //
                .withClient("client-a") // client_id
                .secret("$2a$10$GqJuJhe7zmtwwThIed7smu9zMJBgSQzFMP47eEDL.g9tg8Y82.A7m") // client_secret
                .authorizedGrantTypes("authorization_code")  //采用什么方式获取access_token
                .redirectUris("http://localhost:8081/clientA/login")
                .scopes("all")
                .autoApprove(true) //自动授权
                .and()
                .withClient("client-b") // client_id
                .secret("$2a$10$GqJuJhe7zmtwwThIed7smu9zMJBgSQzFMP47eEDL.g9tg8Y82.A7m") // client_secret
                .authorizedGrantTypes("authorization_code")  //采用什么方式获取access_token
                .redirectUris("http://localhost:8082/clientB/login")
                .scopes("all")
                .autoApprove(true);
    }
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(passwordEncoder);
    }
}

安全配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 该bean的作用是,在UserDetailsService接口的loadUserByUsername返回的UserDetail中包含了
     * password, 该bean就将用户从页面提交过来的密码进行处理,处理之后与UserDetail中密码进行比较。
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    // 统一安全配置
    @Override
    pro     .and()
                .authorizeRequests()
                .antMatchers("/login.html", "/images/**").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();
    }
    // 密码加密处理
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        System.out.println(bCryptPasswordEncoder.encode("1"));
    }
}

自定义认证

// 该类的作用是处理用户登录名和密码
@Component
public class UserSecurityService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = new User(username,
                "$2a$10$GqJuJhe7zmtwwThIed7smu9zMJBgSQzFMP47eEDL.g9tg8Y82.A7m",
                Arrays.asList(new SimpleGrantedAuthority("ROLE_user"), new SimpleGrantedAuthority("ROLE_admin")));
        return user;
    }
}

登录页面

<body>
    <h1>用户登录</h1>
    <form action="authentication/form" method="post">
        username: <input name="username"/><br>
        Password: <input name="password"/><br>
        <button>提交</button>
    </form>
</body>

1.2 子系统client-a

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <version>2.3.6.RELEASE</version>
    </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>
        <version>2.1.9.RELEASE</version>
    </dependency>
</dependencies>

配置

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

security:
  oauth2:
    client:
      client-id: client-a
      client-secret: 1
      # 在每个服务器中要保存 access_token
      access-token-uri: http://localhost:7070/auth/oauth/token
      user-authorization-uri: http://localhost:7070/auth/oauth/authorize

    resource:
      jwt:
        key-value: abcxyz  #验签

启动类配置

@SpringBootApplication
@EnableOAuth2Sso
// 启动权限认证
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled=true)
public class SsoClientApplicationA {
    public static void main(String[] args) {
        SpringApplication.run(SsoClientApplicationA.class, args);
    }
}

controller示例

@RequestMapping
@RestController
public class UserController {
    @RequestMapping("/user")
    @PreAuthorize("hasAuthority('ROLE_admin')")
    public Object get(Authentication authentication) {
        System.out.println("ClientA: " + authentication.getName() + ";;" + authentication.getAuthorities());
        return "clientA";
    }
    @RequestMapping("/login")
    @PreAuthorize("hasAuthority('ROLE_admin')")
    public Object hello(Authentication authentication) {
        System.out.println("ClientA: " + authentication.getName() + ";;" + authentication.getAuthorities());
        return "hello_A";
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <H1>应用AAAA</H1>
    <a href="http://localhost:8082/clientB">访问B服务</a>
</body>
</html>

1.2 子系统client-b

依赖

        <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</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.9.RELEASE</version>
        </dependency>

yml配置

server:
  port: 8082
  servlet:
    context-path: /clientB

security:
  oauth2:
    client:
      client-id: client-b
      client-secret: 1
      access-token-uri: http://localhost:7070/auth/oauth/token
      user-authorization-uri: http://localhost:7070/auth/oauth/authorize

    resource:
      jwt:
        key-value: abcxyz  #验签

启动类配置

@SpringBootApplication
@EnableOAuth2Sso
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled=true)
public class SsoClientApplicationB {
    public static void main(String[] args) {
        SpringApplication.run(SsoClientApplicationB.class, args);
    }
}

controller示例

@RestController
@RequestMapping
public class UserController {

    @RequestMapping("/info")
    @RolesAllowed("ROLE_admin")
    public Object get(Authentication authentication) {
        System.out.println("ClientB: " + authentication.getName() + ";;" + authentication.getAuthorities());
        return "clientA";
    }
    @RequestMapping
    @RolesAllowed("ROLE_admin")
    public Object hello(Authentication authentication) {
        System.out.println("ClientB: " + authentication.getName() + ";;" + authentication.getAuthorities());
        return "hello_B";
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <H1>应用B</H1>
    <a href="http://localhost:8081/clientA">访问A服务</a>
</body>
</html>

源码: 链接:https://pan.baidu.com/s/1hDeo-gMwuStKaL1mniUDog&shfl=sharepset 密码:t4s4