授权之基于JWT完成单点登录

  • 摘要
  • 单点登录简介
  • 创建oauth2-client
  • 网页单点服务验证
  • 服务调用验证


摘要

Spring Cloud Security 为构建安全的SpringBoot应用提供了一系列解决方案,结合Oauth2可以实现单点登录功能,本文将对其单点登录用法进行详细介绍。

单点登录简介

单点登录简称SSO,只的是当有多个系统需要登录时,用户只需要登录一个系统就无须登录其他的系统

创建oauth2-client

这里新建一个oauth2-client作为客服端,使用oauth2-jwt-server服务作授权服务,当我们在oauth2-jwt-server登录了,就可以直接调用oauth2-client的接口,反之,则跳转oauth2-jwt-server进行登录

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
    </dependencies>
server:
  port: 9300
  servlet:
    session:
      cookie:
        # 防止cookie冲突,冲突会导致登录验证不通过
        name: OAUTH2-CLIENT-SESSIONID
spring:
  application:
    name: oauth-client
oauth2-service-url: http://localhost:9200
security:
  oauth2:
    client:
      # 申请的client 的id(对应jwt服务的client-id)
      client-id: admin
      # 申请的client 的秘钥(对应jwt服务的client-secret)
      client-secret: admin
      # 获取code的地址
      user-authorization-uri: ${oauth2-service-url}/oauth/authorize

      # 拿到code获取access-token的URL
      access-token-uri: ${oauth2-service-url}/oauth/token
    resource:
      jwt:
        key-uri: ${oauth2-service-url}/oauth/token_key

启动类需要加上@EnableOAuth2Sso表示开启SSO验证

@EnableOAuth2Sso
@SpringBootApplication
public class Oauth2ClientApplication {

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

添加用于验证的客户端接口

@RestController
@RequestMapping("/user")
public class UserController {

	@GetMapping("/getUser")
	Object getUser(Authentication authentication) {
		return authentication;
	}

}

修改jwt服务的授权服务器(AuthorizationServerConfig)

package com.zglx.jwt.config;

import com.zglx.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 授权服务器
 * @Author: Zlx
 * @Date: 2021/10/17 5:09 下午
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
	@Autowired
	private PasswordEncoder passwordEncoder;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private UserService userService;

	@Autowired
	@Qualifier("redisTokenStore")
	private TokenStore tokenStore;

	@Qualifier("jwtTokenStore")
	@Autowired
	private TokenStore jwtTokenStore;

	@Autowired
	@Qualifier("jwtAccessTokenConverter")
	private JwtAccessTokenConverter jwtAccessTokenConverter;

	@Autowired
	private JwtTokenEnhancer jwtTokenEnhancer;


	/**
	 * 使用密码模式需要配置
	 *
	 * @param endpoints endpoints
	 * @throws Exception
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//		// 自定义JWT内容增强器
		TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
		List<TokenEnhancer> delegates = new ArrayList<>();
		// 配置jwt内容增强器
		delegates.add(jwtTokenEnhancer);
		delegates.add(jwtAccessTokenConverter);
		tokenEnhancerChain.setTokenEnhancers(delegates);
		endpoints.authenticationManager(authenticationManager)
				// 配置用户数据源,登录的时候会执行userService的loadUserByUsername方法
				// 登录地址
				// http://localhost:9401/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all&state=normal
				.userDetailsService(userService)
				// 设置存储方式,这里使用redis
//				.tokenStore(tokenStore);
				.tokenStore(jwtTokenStore)
				.tokenEnhancer(tokenEnhancerChain)
				// 设置自定义token构造器
				.accessTokenConverter(jwtAccessTokenConverter);
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.inMemory()
				// 配置client_id
				.withClient("admin")
				// 配置client_secret
				.secret(passwordEncoder.encode("admin"))
				// 配置访问token的有效期
				.accessTokenValiditySeconds(3600)
				// 配置刷新token的有效期
				.refreshTokenValiditySeconds(864000)
				// 自动授权配置(api调用需要打开此配置,不然一直都要手动确认授权,如果是)
				.autoApprove(true)
				// 配置redirect_uri,用于授权成功后的跳转
				.redirectUris("http://localhost:9300/login")
				// 配置申请的权限范围
				.scopes("all")
				// 配置grant_type,表示授权类型,
				.authorizedGrantTypes("authorization_code", "password", "refresh_token");
	}

	/**
	 * 单点登录时必须验证
	 *
	 * @param security security
	 * @throws Exception
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security.tokenKeyAccess("isAuthenticated()");
	}
}

网页单点服务验证

访问客户端需要授权的接口http://localhost:9300/user/getUser会跳转到授权服务的登录界面;

spring auth2 单点登录 springcloud oauth2单点登录_spring boot


spring auth2 单点登录 springcloud oauth2单点登录_spring auth2 单点登录_02


spring auth2 单点登录 springcloud oauth2单点登录_spring cloud_03

服务调用验证

spring auth2 单点登录 springcloud oauth2单点登录_spring boot_04


autoApprove必须打开,不然服务调用无法通过授权

spring auth2 单点登录 springcloud oauth2单点登录_java_05


spring auth2 单点登录 springcloud oauth2单点登录_spring boot_06


spring auth2 单点登录 springcloud oauth2单点登录_spring_07


登录成功后,postMan会自动生成一个token放到Headers的Authorization中

spring auth2 单点登录 springcloud oauth2单点登录_spring boot_08


最后请求成功

{
  "authorities": [
    {
      "authority": "admin"
    }
  ],
  "details": {
    "remoteAddress": "0:0:0:0:0:0:0:1",
    "sessionId": "581EE1EE460FB72C2FD165393A0BC9B7",
    "tokenValue": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6Z2x4Iiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYzNTg3MDAwMSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiODA0MTllOTgtZjA3My00Mjc4LTg0ZWItMjU1YWVjZjAzZTYwIiwiY2xpZW50X2lkIjoiYWRtaW4iLCJlbmhhbmNlIjoiZW5oYW5jZSBpbmZvIn0.wPmxNkAhtVPZNuhigaQYXIBaQigP7JPqtwVu2mCrHN8",
    "tokenType": "bearer",
    "decodedDetails": null
  },
  "authenticated": true,
  "userAuthentication": {
    "authorities": [
      {
        "authority": "admin"
      }
    ],
    "details": null,
    "authenticated": true,
    "principal": "zglx",
    "credentials": "N/A",
    "name": "zglx"
  },
  "oauth2Request": {
    "clientId": "admin",
    "scope": [
      "all"
    ],
    "requestParameters": {
      "client_id": "admin"
    },
    "resourceIds": [
      
    ],
    "authorities": [
      
    ],
    "approved": true,
    "refresh": false,
    "redirectUri": null,
    "responseTypes": [
      
    ],
    "extensions": {
      
    },
    "grantType": null,
    "refreshTokenRequest": null
  },
  "clientOnly": false,
  "principal": "zglx",
  "credentials": "",
  "name": "zglx"
}```

# oauth2-client添加权限校验
需要给client添加一个配置,用来添加方法级角色权限校验

```java
package com.example.oauth2client.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @Description:
 * @Author: Zlx
 * @Date: 2021/10/24 10:28 下午
 */
@Configuration
@Order(101)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

新增角色权限验证接口

@GetMapping("/testRole")
	@PreAuthorize("hasAuthority('admin')")
	Object testRole(Authentication authentication) {
		return "Has admin auth!";
	}

如果postMan生成的token不是admin角色用户的,将返回如下信息

spring auth2 单点登录 springcloud oauth2单点登录_spring cloud_09