Spring Security + OAuth2.0 + JWT 实现单点登录

在这里插入图片描述
附:源代码 Github 地址:Spring Security + OAuth2.0 + JWT 实现单点登录

一、认证服务
  • 配置文件

    bootstrap.yml

server:
  port: 8081

spring:
  main:
    allow-bean-definition-overriding: true

​ pom.xml

	<dependencyManagement>
		<dependencies>
			<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Greenwich.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>2.1.0.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

		<!--lombok start-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<!--lombok end-->
		<!--导⼊spring cloud oauth2依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-oauth2</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.security.oauth.boot</groupId>
					<artifactId>spring-security-oauth2-autoconfigure</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth.boot</groupId>
			<artifactId>spring-security-oauth2-autoconfigure</artifactId>
			<version>2.1.11.RELEASE</version>
		</dependency>

	</dependencies>
  • 配置 WebSecurityConfigurer
package com.natsumes.ame.config;

import com.natsumes.ame.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 该配置类,主要处理用户名和密码的校验等事宜
 */
@EnableWebSecurity(debug = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {


    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

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

    /**
     * 配置拦截资源
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.requestMatchers().antMatchers("/**")
                .and().authorizeRequests()
                .antMatchers("/oauth/**").permitAll() // /oauth/**接口对外开放
                .anyRequest().authenticated()   // 在创建过滤器的基础上的一些自定义配置
                .and().formLogin()
                .and().httpBasic(); //Basic认证
    }

    /**
     * 处理用户名和密码验证事宜
     * 1)客户端传递username和password参数到认证服务器
     * 2)一般来说,username和password会存储在数据库中的用户表中
     * 3)根据用户表中数据,验证当前传递过来的用户信息的合法性
     * 可以通过自定义用户校验类MyUserDetailsService来自定义用户信息
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
        auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 密码解析器,这里使用 BCryptPasswordEncoder 加密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • 自定义UserDetailsService
package com.natsumes.ame.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;


@Slf4j
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * Locates the user based on the username. In the actual implementation, the search
     * may possibly be case sensitive, or case insensitive depending on how the
     * implementation instance is configured. In this case, the <code>UserDetails</code>
     * object that comes back may have a username that is of a different case than what
     * was actually requested..
     *
     * 可以根据自身业务实现认证
     *
     * @param username the username identifying the user whose data is required.
     * @return a fully populated user record (never <code>null</code>)
     * @throws UsernameNotFoundException if the user could not be found or the user has no
     *                                   GrantedAuthority
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username={}", username);
        if (!username.equals("admin")) {
            throw new UsernameNotFoundException("the username is not found");
        }
        //用户角色在数据库中获取
        String role = "ROLE_ADMIN";
        ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(role));
        //线上环境应该通过用户名查询数据库获取加密后的密码
        String password = passwordEncoder.encode("123456");
        return new User(username, password, authorities);
    }
}

  • 配置AuthorizationServerConfigurer
package com.natsumes.ame.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jwt.crypto.sign.MacSigner;
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.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     *
     * 认证服务器最终是以api接⼝的⽅式对外提供服务(校验合法性并⽣成令牌、校验令牌等)
     * 那么,以api接⼝⽅式对外的话,就涉及到接⼝的访问权限,我们需要在这⾥进⾏必要的配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
        security.allowFormAuthenticationForClients()    // 允许客户端表单认证
                .tokenKeyAccess("permitAll()")  // 开启端口/oauth/token_key的访问权限(允许)
                .checkTokenAccess("permitAll()");   // 开启端口/oauth/check_token的访问权限(允许)
    }

    /**
     * 客户端详情配置
     * ⽐如client_id,secret
     * 当前这个服务就如同QQ平台,本网站作为客户端需要qq平台进⾏登录授权认证等,提前需到QQ平台注册,QQ平台会给我们
     * 颁发client_id等必要参数,表明客户端是谁
     * @param clients   client  配置
     * @throws Exception    异常信息
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //可通过数据库配置
        clients.inMemory()
                .withClient("order-client")
                .resourceIds("order-server", "user-server", "oauth-server")
                .secret(passwordEncoder.encode("order-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password", "implicit", "client_credentials")
                .accessTokenValiditySeconds(3600)
                .scopes("all")
                .autoApprove(true)  //自动确认授权
                //自动认证并跳转获取token
                .redirectUris("http://127.0.0.1:8081/oauth/token?grant_type=authorization_code&client_id=order-client&client_secret=order-secret-8888")
                .and()
                .withClient("user-client")
                .resourceIds("order-server", "user-server", "oauth-server")
                .secret(passwordEncoder.encode("user-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password", "implicit", "client_credentials")
                .accessTokenValiditySeconds(3600)
                .scopes("all")
                .autoApprove(true)
                .redirectUris("http://www.baidu.com")
                .and().build();
        //clients.withClientDetails();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // jwt token 方式
        endpoints.authenticationManager(authenticationManager)
                .tokenServices(authorizationServerTokenServices())
                //.userDetailsService(myUserDetailsService)
                .tokenStore(tokenStore())
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test123");
        converter.setVerifier(new MacSigner("test123"));
        return converter;
    }

    /**
     * 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
     */
    private AuthorizationServerTokenServices authorizationServerTokenServices() {
        // 使用默认实现
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
        defaultTokenServices.setTokenStore(tokenStore());

        // 针对jwt令牌的添加
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());

        // 设置令牌有效时间(一般设置为2个小时)
        defaultTokenServices.setAccessTokenValiditySeconds(2 * 60 * 60); // access_token就是我们请求资源需要携带的令牌
        // 设置刷新令牌的有效时间
        defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天

        return defaultTokenServices;
    }
}
二、Spring Security OAuth2.0 的四种授权方式
2.1 授权码模式
2.1.1 将获取code和token合并到一步中

http://localhost:8081/oauth/authorize?response_type=code&client_id=order-client&scope=all

2.1.2 获取code, 换取 token

首先获取授权code,然后通过code获取token

http://localhost:8081/oauth/authorize?response_type=code&client_id=user-client&scope=all&redirect_uri=http://www.baidu.com

浏览器跳转到 https://www.baidu.com/?code=DctdGQ

使用 code 获取 token

http://localhost:8081/oauth/token?grant_type=authorization_code&code=DctdGQ&username=admin&password=123456&client_id=user-client&client_secret=user-secret-8888&redirect_uri=http://www.baidu.com

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjEwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiYjlhZTJjMzMtNjBhOS00ZTAwLThlNjYtNzU2M2RjODg1MmU3IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.BXoQBuDFbx7x81ShSZaRM5FvaFWAYWEXEUgPlP2-UMk",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNmVjZDk5MmEtOGI1Zi00NjA2LTk1NmItMmNhYjk4MzI0Yjg2IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiYjlhZTJjMzMtNjBhOS00ZTAwLThlNjYtNzU2M2RjODg1MmU3In0.U6LNCmjZNqlxLc3MzNanNaJrJo7PbAwMK-as07QPih0",
    "expires_in":7199,
    "scope":"all",
    "jti":"b9ae2c33-60a9-4e00-8e66-7563dc8852e7"
}
2.2 密码模式

http://localhost:8081/oauth/token?grant_type=password&username=admin&password=123456&client_id=order-client&client_secret=order-secret-8888

{
  "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjEyMjgsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiZjhmNjkwNzItNjk2My00MGE5LWE5YWUtMWE5YzQ1MzI1OTA2IiwiY2xpZW50X2lkIjoib3JkZXItY2xpZW50Iiwic2NvcGUiOlsiYWxsIl19.nMvNO76FZl0y0mCUZNv9TBvw7dxJqTk69xGAQ45ZTZc",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMyMjgsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNjk1Y2E4M2QtZjVjMS00MjM4LThiMDYtOTkwMDk4MTJkZGU4IiwiY2xpZW50X2lkIjoib3JkZXItY2xpZW50Iiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImY4ZjY5MDcyLTY5NjMtNDBhOS1hOWFlLTFhOWM0NTMyNTkwNiJ9.owg9G_N1CWvToQWcxnY-Aq-pLXtaLNbM6qhrsqpAaiE",
    "expires_in":7199,
    "scope":"all",
    "jti":"f8f69072-6963-40a9-a9ae-1a9c45325906"
}
2.3 简化模式

http://localhost:8081/oauth/authorize?response_type=token&client_id=user-client&client_secret=user-secret-8888&scope=all&redirect_uri=http://www.baidu.com

浏览器返回

https://www.baidu.com/#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjEzNDgsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiMWZkNDc2MzgtODI2OC00ZWU2LTkxYmItYzdlMDQ3MTQ0MmIwIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.ujUPZOWgWwhuXYmWtmV0l4d9308S1gzeJodZLQglJBA&token_type=bearer&expires_in=7199&jti=1fd47638-8268-4ee6-91bb-c7e0471442b0

2.4 客户端模式

http://localhost:8081/oauth/token?client_id=user-client&client_secret=user-secret-8888&grant_type=client_credentials

{ 	    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNTk1MzIxNDMyLCJqdGkiOiJjNTljMGM5NS0yNDgwLTQ4NWQtOWZhYS0zMjE5OTllNDJmYTYiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.dHMRaw4jXwGGEsQJxokAHd-CLKYjT71ZW-17Baw9THw",
    "token_type":"bearer",
    "expires_in":7199,
    "scope":"all",
    "jti":"c59c0c95-2480-485d-9faa-321999e42fa6"
}
2.5 带 token 访问
package com.natsumes.ame.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class Controller {
    @GetMapping("/auto/test")
    public String test1() {
        return "auto test success: " + new Date();
    }

    @GetMapping("/demo/test")
    public String test2() {
        return "demo test success: " + new Date();
    }

    @GetMapping("test3")
    public String test3() {
        return "test3 success: " + new Date();
    }
}

不需要认证:

http://localhost:8081/test3

test3 success: Tue Jul 21 15:44:53 CST 2020

需要认证:

不带 token 访问

http://localhost:8081/auto/test

{
    "timestamp": "2020-07-21T07:32:20.383+0000",
    "status": 401,
    "error": "Unauthorized",
    "message": "Unauthorized",
    "path": "/auto/test"
}

http://localhost:8081/demo/test

{ 
	"timestamp": "2020-07-21T07:34:24.209+0000",
  	"status": 401,
  	"error": "Unauthorized",
  	"message": "Unauthorized",
    "path": "/demo/test"
}

带 token 访问

http://localhost:8081/demo/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU

demo test success: Tue Jul 21 15:47:21 CST 2020
2.6 检验和刷新 token

检验 token

http://localhost:8081/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU

{
    "user_name": "admin",
    "scope": [
        "all"
    ],
    "active": true,
    "exp": 1595324824,
    "authorities": [
        "ROLE_ADMIN"
    ],
    "jti": "c7376e22-e94d-4cf9-b52e-7f89c492ecc2",
    "client_id": "order-client"
}

刷新 token

http://localhost:8081/oauth/token?grant_type=refresh_token&client_id=user-client&client_secret=user-secret-8888&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNmVjZDk5MmEtOGI1Zi00NjA2LTk1NmItMmNhYjk4MzI0Yjg2IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiYjlhZTJjMzMtNjBhOS00ZTAwLThlNjYtNzU2M2RjODg1MmU3In0.U6LNCmjZNqlxLc3MzNanNaJrJo7PbAwMK-as07QPih0

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjYzNTksInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNzk2ZWY0M2QtMzUxOS00N2QxLTg0MTYtNzQ3MThhMTJjMjNjIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.SPR1_o0nVegPgziVrIwLy4NILq-NWHClm97RnBfNUY0",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTU1NzMwNDQsInVzZXJfbmFtZSI6ImFkbWluIiwianRpIjoiNmVjZDk5MmEtOGI1Zi00NjA2LTk1NmItMmNhYjk4MzI0Yjg2IiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiNzk2ZWY0M2QtMzUxOS00N2QxLTg0MTYtNzQ3MThhMTJjMjNjIn0.mxnB-BUqfdqEfZMaoC75gPKRLNGBUMO1f_E7GelE9rA",
    "expires_in": 7199,
    "scope": "all",
    "jti": "796ef43d-3519-47d1-8416-74718a12c23c"
}
2.7 资源服务器改造

将认证服务改造为资源服务器

package com.natsumes.ame.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@EnableResourceServer
@Configuration
@EnableWebSecurity
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
        resources.resourceId("oauth-server").tokenStore(tokenStore).stateless(true);
    }

    /**
     * 场景:一个服务中可能有很多资源(API接口)
     *    某一些API接口,需要先认证,才能访问
     *    某一些API接口,压根就不需要认证,本来就是对外开放的接口
     *    我们就需要对不同特点的接口区分对待(在当前configure方法中完成),设置是否需要经过认证
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http    // 设置session的创建策略(根据需要创建即可)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .authorizeRequests()
                .antMatchers("/auto/**").authenticated() // auto为前缀的请求需要认证
                .antMatchers("/demo/**").authenticated() // demo为前缀的请求需要认证
                .anyRequest().permitAll();
    }

}

不需要认证:

http://localhost:8081/test3

test3 success: Tue Jul 21 17:07:15 CST 2020

需要认证:

http://localhost:8081/demo/test

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

带token访问:

http://localhost:8081/demo/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU

demo test success: Tue Jul 21 17:09:35 CST 2020
三、Oauth2.0 + JWT 实现分布式登录
3.1 资源服务器

上面已经搭建好认证服务,接下来再创建两个服务 user-server 和 order-server 作为资源服务器

以 order-server 为例

package com.natsumes.ame.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@EnableResourceServer
@Configuration
@EnableWebSecurity
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
        resources.resourceId("order-server").tokenStore(tokenStore).stateless(true);
    }

    /**
     * 场景:一个服务中可能有很多资源(API接口)
     *    某一些API接口,需要先认证,才能访问
     *    某一些API接口,压根就不需要认证,本来就是对外开放的接口
     *    我们就需要对不同特点的接口区分对待(在当前configure方法中完成),设置是否需要经过认证
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http    // 设置session的创建策略(根据需要创建即可)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .authorizeRequests()
                .antMatchers("/order/need-oauth/**").authenticated() // auto为前缀的请求需要认证
                .anyRequest().permitAll();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test123");
        converter.setVerifier(new MacSigner("test123"));
        return converter;
    }
}

package com.natsumes.ame.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping("/no-oauth/test")
    public String test1() {
        return new Date() + ": order test1 success";
    }

    @GetMapping("/need-oauth/test")
    public String test2() {
        return new Date() + ": order test2 success";
    }
}

3.2 测试

不需要认证:

http://localhost:8083/order/no-oauth/test

Tue Jul 21 17:19:25 CST 2020: order test1 success

需要认证:

http://localhost:8083/order/need-oauth/test

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

带token访问:

http://localhost:8083/order/need-oauth/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTUzMjQ4MjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJjNzM3NmUyMi1lOTRkLTRjZjktYjUyZS03Zjg5YzQ5MmVjYzIiLCJjbGllbnRfaWQiOiJvcmRlci1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.S_EnUvucm9wy-stS4jcKz8MZUJOOzYv_PGk4iSfDmYU

Tue Jul 21 17:21:33 CST 2020: order test2 success
  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值