在前文《基于Spring Security和 JWT的权限系统设计》之中已经讨论过基于 Spring Security
和 JWT
的权限系统用法和实践,本文则进一步实践一下基于 Spring Security Oauth2
实现的多系统单点登陆(SSO
)和 JWT
权限控制功能,毕竟这个需求也仍是蛮广泛的。html
代码已开源,放在文尾,须要自取git
在此以前须要学习和了解一些前置知识包括:github
Spring
实现的 Web
系统的认证和权限模块authorization
)的开放网络标准JSON
的开放标准((RFC 7519
),用于做为JSON
对象在不一样系统之间进行安全地信息传输。主要使用场景通常是用来在 身份提供者和服务提供者间传递被认证的用户身份信息Server
),用于完成用户登陆,认证和权限处理Client
)SSO
”)基于此目标驱动,本文设计三个独立服务,分别是:spring
codesheep-server
)codesheep-client1
)codesheep-client2
)三个应用经过一个多模块的 Maven
项目进行组织,其中项目父 pom
中须要加入相关依赖以下:数据库
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Cairo-RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
复制代码
项目结构以下:浏览器
受权认证中心本质就是一个 Spring Boot
应用,所以须要完成几个大步骤:缓存
pom
中添加依赖<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
复制代码
yml
配置文件:server:
port: 8085
servlet:
context-path: /uac
复制代码
即让受权中心服务启动在本地的 8085
端口之上安全
@Component
public class SheepUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
if( !"codesheep".equals(s) )
throw new UsernameNotFoundException("用户" + s + "不存在" );
return new User( s, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_NORMAL,ROLE_MEDIUM"));
}
}
复制代码
这里建立了一个用户名为codesheep
,密码 123456
的模拟用户,而且赋予了 普通权限(ROLE_NORMAL
)和 中等权限(ROLE_MEDIUM
)bash
AuthorizationServerConfig
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 定义了两个客户端应用的通行证
clients.inMemory()
.withClient("sheep1")
.secret(new BCryptPasswordEncoder().encode("123456"))
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all")
.autoApprove(false)
.and()
.withClient("sheep2")
.secret(new BCryptPasswordEncoder().encode("123456"))
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all")
.autoApprove(false);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 一天有效期
endpoints.tokenServices(tokenServices);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("isAuthenticated()");
}
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("testKey");
return converter;
}
}
复制代码
这里作的最重要的两件事:一是 定义了两个客户端应用的通行证(sheep1
和sheep2
);二是 配置 token
的具体实现方式为 JWT Token
。服务器
SpringSecurityConfig
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setHideUserNotFoundExceptions(false);
return authenticationProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
.and()
.authorizeRequests()
.antMatchers("/oauth/**").authenticated()
.and()
.formLogin().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
复制代码
本文建立两个客户端应用:codesheep-client1
和codesheep-client2
,因为二者相似,所以只以其一为例进行讲解
ClientWebsecurityConfigurer
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableOAuth2Sso
public class ClientWebsecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.anyRequest().authenticated();
}
}
复制代码
复杂的东西都交给注解了!
auth-server: http://localhost:8085/uac
server:
port: 8086
security:
oauth2:
client:
client-id: sheep1
client-secret: 123456
user-authorization-uri: ${auth-server}/oauth/authorize
access-token-uri: ${auth-server}/oauth/token
resource:
jwt:
key-uri: ${auth-server}/oauth/token_key
复制代码
这里几项配置都很是重要,都是须要和前面搭建的受权中心进行通讯的
TestController
@RestController
public class TestController {
@GetMapping("/normal")
@PreAuthorize("hasAuthority('ROLE_NORMAL')")
public String normal( ) {
return "normal permission test success !!!";
}
@GetMapping("/medium")
@PreAuthorize("hasAuthority('ROLE_MEDIUM')")
public String medium() {
return "medium permission test success !!!";
}
@GetMapping("/admin")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String admin() {
return "admin permission test success !!!";
}
}
复制代码
此测试控制器包含三个接口,分别须要三种权限(ROLE_NORMAL
、ROLE_MEDIUM
、ROLE_ADMIN
),待会后文会一一测试看效果
codesheep-server
(启动于本地8085
端口)codesheep-client1
(启动于本地8086
端口)codesheep-client2
(启动于本地8087
端口)首先用浏览器访问客户端1 (codesheep-client1
) 的测试接口:localhost:8086/normal
,因为此时并无过用户登陆认证,所以会自动跳转到受权中心的登陆认证页面:http://localhost:8085/uac/login
:
输入用户名 codesheep
,密码 123456
,便可登陆认证,并进入受权页面:
赞成受权后,会自动返回以前客户端的测试接口:
此时咱们再继续访问客户端1 (codesheep-client1
) 的测试接口:localhost:8086/medium
,发现已经直接能够调用而无需认证了:
因为 localhost:8086/normal
和 localhost:8086/medium
要求的接口权限,用户codesheep
均具有,因此能顺利访问,接下来再访问一下更高权限的接口:localhost:8086/admin
:
好了,访问客户端1 (codesheep-client1
) 的测试接口到此为止,接下来访问外挂的客户端2 (codesheep-client2
) 的测试接口:localhost:8087/normal
,会发现此时会自动跳到受权页:
受权完成以后就能够顺利访问客户端2 (codesheep-client2
) 的接口:
这就验证了单点登陆SSO
的功能了!
受篇幅所限,本文应该说实践了一下精简流程的:SSO
单点登陆和JWT
权限控制,还有不少能够复杂和具化的东西能够实现,好比:
client
凭据 和 用户 user
的凭据能够用数据库进行统一管理token
也能够用数据库或缓存进行统一管理总之,尽情地折腾去吧!
因为能力有限,如有错误或者不当之处,还请你们批评指正,一块儿学习交流!