日常bb

日常bb

Spring Security 单体应用(认证授权服务+资源服务)

2022-10-04
Spring Security 单体应用(认证授权服务+资源服务)

Spring Security 单体应用(认证授权服务+资源服务)

有了前面几篇文章的理论支持,是时候上实战了。

首先以单体的视角来进行集成,也就是认证服务和资源服务在一起,是一个单体项目。

auth-resources-demo Github地址

环境

父类 pom 控制所有依赖版本。

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.4</version>
	<relativePath/>
</parent>

<properties>
	<java.version>11</java.version>
	<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
	<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
	<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
</properties>

<dependencyManagement>
	<dependencies>
		<!-- spring cloud alibaba 依赖 -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-alibaba-dependencies</artifactId>
			<version>${spring-cloud-alibaba.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		<!-- spring cloud 依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

子类依赖主要是spring-boot-starter-webspring-security-oauth2-autoconfigure
通过微服务版本限定后spring-security-oauth2-autoconfigure的最终版本自动适配为2.1.2。

<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.security.oauth.boot</groupId>
		<artifactId>spring-security-oauth2-autoconfigure</artifactId>
	</dependency>

	<!--server-api-->
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>javax.servlet-api</artifactId>
	</dependency>
</dependencies>

授权配置和测试

需要什么配置就写什么配置,主要是梳理流程。

1、授权服务核心配置

AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
}

2、Spring Security 核心配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

3、访问测试

http://127.0.0.1:8080/oauth/authorize

please sign in

任意输入账号密码,会提示账号或密码错误。

4、完善配置

WebSecurityConfig,因为重点是在于梳理流程,所以用户信息使用硬编码方式。

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

@Autowired
public PasswordEncoder passwordEncoders() {
    return new BCryptPasswordEncoder();
}


@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
    // 配置全局用户信息
    auth.inMemoryAuthentication().withUser("admin")
            .password(passwordEncoders().encode("123456"))
            .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("user_add,user_update,ROLE_ADMIN"));
}

AuthorizationServerConfig,授权服配置端点信息。

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    // 基于内存便于测试,使用in-memory存储
    clients.inMemory()
            // client_id
            .withClient("rcbb")
            // 未加密
            //.secret("secret")
            // 加密
            .secret(passwordEncoder.encode("rcbb"))
            // 资源列表
            //.resourceIds("res1")
            // 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
            .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
            // 允许的授权范围
            .scopes("all", "ROLE_ADMIN", "ROLE_USER")
            // false跳转到授权页面
            //.autoApprove(false)
            //加上验证回调地址
            .redirectUris("http://rcbb.cc");
}

5、重启服务测试

这次我们使用授权码方式进行测试,加上一些参数。

response_type=code
client_id=rcbb
redirect_uri=http://rcbb.cc
scope=all

http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=rcbb&redirect_uri=http://rcbb.cc&scope=all

出现登录页面,输入我们WebSecurityConfig配置的用户信息admin/123456

OAuth Approval

然后就会成功,并且让我们选择是否授权。

授权成功后,会跳到对应的网址,并且url会接上我们所需的code

rcbb

拿到授权码后,进行获取token的测试。我使用的是PostMan工具进行测试。

首先需要在Authorization上选择对应的Type并填上AuthorizationServerConfig中配置的client信息。

postman

然后Body体以x-www-form-urlencoded的方式,填上对应的参数。

postman

请求的curl

curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Authorization: Basic cmNiYjpyY2Ji' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=fB3uZl' \
--data-urlencode 'redirect_uri=http://rcbb.cc' \
--data-urlencode 'scope=all' \
--data-urlencode 'client_id=rcbb'

测试成功,成功获取token

6、配置密码模式

使用密码模式,发现并不支持。

不支持密码模式

WebSecurityConfig补充配置认证管理器AuthenticationManager

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

AuthorizationServerConfig补充配置密码模式。

@Autowired
private AuthenticationManager authenticationManager;

/**
 * 配置令牌的访问端点和令牌服务
 *
 * @param endpoints the endpoints configurer
 * @throws Exception
 */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // 配置认证管理器
    endpoints.authenticationManager(authenticationManager);
}

重启服务进行测试。

通过密码获取token

请求的curl

curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Authorization: Basic cmNiYjpyY2Ji' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=admin' \
--data-urlencode 'password=123456'

7、测试简化模式

请求参数:

response_type=token
client_id=rcbb
redirect_uri=http://rcbb.cc
scope=all

http://127.0.0.1:8080/oauth/authorize?response_type=token&client_id=rcbb&redirect_uri=http://rcbb.cc&scope=all

授权成功后,token的信息直接返回在url上了。

简化模式

8、测试客户端模式

客户端模式

请求的curl

curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Authorization: Basic cmNiYjpyY2Ji' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=rcbb' \
--data-urlencode 'client_secret=rcbb'

认证授权配置和测试

1、创建测试资源

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

    @PostMapping
    public String save() {
        return "save() success";
    }

    @PutMapping
    public String update() {
        return "update() success";
    }

    @GetMapping
    public String list() {
        return "list() success";
    }
    
}

三个请求:

  • post:/user 新增用户信息
  • put:/user 修改用户信息
  • get:/user 获取用户信息

2、获取token后访问资源

先获取token

通过密码获取token

虽然请求中携带了token值,但是还是401 Unauthorized

401 Unauthorized

这是为啥???

正常情况下,认证授权服务和资源服务是分开的。

正常认证授权服务是接管/oauth/token/oauth/token_key/oauth/check_token,这三个请求,也就是这三个请求会被Spring Security OAuth2处理。

然后我们自己写的资源应该是在资源服务上,例如/user请求就会被Spring Security处理。

那这种单体情况,如何处理?

3、新增资源服务配置

认证授权服务和资源服务写在一起。

ResourceServerConfig资源服务配置。

@Configuration
@EnableResourceServer
// 开启注解鉴权
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    //配置资源服安全规则
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 授权的请求
                .authorizeRequests()
                // 任何请求
                .anyRequest()
                // 需要身份认证
                .authenticated()
                .and().csrf().disable();
    }
}

重启服务,再次请求。token错误。

invalid_token

因为默认token是存在内存中(可以配置存储在MySQL、Redis中)的,所以这里重新获取一次token就行了。

请求成功

4、给资源添加权限

@Secured("ROLE_ADMIN"):专门用于判断是否具有角色。

@PreAuthorize("hasAuthority('user_add')")即可判断权限,也可以判断身份,还可以使用正则判断。
如果是判断身份,那么需要填写@PreAuthorize("hasRole('ROLE_ADMIN')")

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

    @PreAuthorize("hasAuthority('user_add')")
    @PostMapping
    public String save() {
        return "save() success";
    }

    @PreAuthorize("hasAuthority('user_remove')")
    @DeleteMapping
    public String remove() {
        return "remove() success";
    }

    @PreAuthorize("hasAuthority('user_update')")
    @PutMapping
    public String update() {
        return "update() success";
    }

    @Secured("ROLE_ADMIN")
    @GetMapping
    public String list() {
        return "list() success";
    }

}

我们目前的用户信息是在WebSecurityConfig硬编码配置的。

@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
	// 配置全局用户信息
	auth.inMemoryAuthentication().withUser("admin")
			.password(passwordEncoders().encode("123456"))
			.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("user_add,user_update,ROLE_ADMIN"));
}

所以对应上面的资源所需权限,DELETE:/user是无法访问的。

无法访问

其他资源可正常访问。