单点登录(Single Sign On),简称为 SSO,SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
我们先创建一个oauth2-server服务作为登录的服务端模块,Spring Cloud Security中有两种存储令牌的方式,一种是使用Redis来存储,另一种是使用JWT来存储,这里我们采用了JWT来存储令牌。
先在pom.xml引入相关依赖
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
在application.yml中进行配置相关文件
server:
port: 9001
spring:
application:
name: oauth2-server
添加UserService实现UserDetailsService接口,用于加载用户信息
@Service
public class UserService implements UserDetailsService {
private List<User> userList;
@Autowired
private PasswordEncoder passwordEncoder;
@PostConstruct
public void init() {
String password = passwordEncoder.encode("123");
userList = new ArrayList<>();
userList.add(new User("admin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")));
userList.add(new User("user", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<User> users = userList.stream().filter(user -> user.getUsername().equals(username)).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(users)) {
return users.get(0);
} else {
throw new UsernameNotFoundException("用户名或密码错误");
}
}
}
添加JWT存储令牌的配置:
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("jwttest_key");//配置JWT使用的秘钥
return accessTokenConverter;
}
}
添加认证服务器配置,使用@EnableAuthorizationServer注解开启配置认证服务器
Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 使用密码模式需要配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore) //配置令牌存储策略
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("root")
.secret(passwordEncoder.encode("123456"))//这里密码需要进行加密
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(10800) //设置刷新令牌失效时间
.redirectUris("http://localhost:9002/login") //单点登录时配置,访问客户端需要授权的接口,会跳转到该路径
.autoApprove(true) //自动授权配置
.scopes("all")
.authorizedGrantTypes("authorization_code","password","refresh_token");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("isAuthenticated()"); // 获取密钥需要身份认证,使用单点登录时必须配置
}
}
添加资源服务器配置,使用@EnableResourceServer注解开启配置资源服务器
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**");//表示该安全规则只针对参数指定的路径进行过滤,因为测试没有前端代码,先这样去做
}
}
添加SpringSecurity配置,允许认证相关路径的访问及表单登录
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
}
添加需要登录的接口用于测试,使用jjwt工具类来解析Authorization头中存储的JWT内容
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getUser")
public Object getUser(Authentication authentication, HttpServletRequest request) {
String header = request.getHeader("Authorization");
String token = StrUtil.subAfter(header, "bearer ", false);//取分隔字符串之后字符串不包括分隔字符串
return Jwts.parser()
.setSigningKey("jwttest_key".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token)
.getBody();
}
}
这里我们创建一个oauth2-client服务作为需要登录的客户端,当我们在oauth2-server服务上登录以后,就可以直接访问oauth2-client需要登录的接口,来演示下单点登录功能。
在pom.xml中添加相关依赖
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
在application.yml中进行配置
server:
port: 9002
servlet:
session:
cookie:
name: OAUTH2-CLIENT-SESSIONID #防止Cookie冲突,冲突会导致登录验证不通过
oauth2-server-url: http://localhost:9001
spring:
application:
name: oauth2-client
security:
oauth2: #与oauth2-server对应的配置
client:
client-id: root
client-secret: 123456
user-authorization-uri: ${oauth2-server-url}/oauth/authorize
access-token-uri: ${oauth2-server-url}/oauth/token
resource:
jwt:
key-uri: ${oauth2-server-url}/oauth/token_key
在启动类上添加@EnableOAuth2Sso注解来启用单点登录功能
@EnableOAuth2Sso
@SpringBootApplication
public class Oauth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ClientApplication.class, args);
}
}
添加测试接口用于获取当前登录用户信息:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getUser")
public Object getUser(Authentication authentication) {
return authentication;
}
}
启动oauth2-client服务和oauth2-server服务;
访问客户端需要授权的接口http://localhost:9002/user/getUser会跳转到授权服务的登录界面;
输入账号密码进行登录操作
发现获取到的令牌已经变成了JWT令牌,将tokenValue拿到https://jwt.io/ 网站上去解析下可以获得其中内容。
{
"user_name": "admin",
"scope": [
"all"
],
"exp": 1587094663,
"authorities": [
"admin"
],
"jti": "67a4a31f-021d-42ba-884e-a0270af68e02",
"client_id": "root"
}
这里我们使用Postman来演示下
{
"authorities": [
{
"authority": "admin"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "EF89129C446E55917A74753E59B5CBF2",
"tokenValue": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE1ODcwOTYzODMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjU3ZDk4YTZlLWYyYmEtNGIyMC04OTQzLTgxYjFhMjQ5MGY5MCIsImNsaWVudF9pZCI6InJvb3QiLCJlbmhhbmNlIjoiZW5oYW5jZSBpbmZvIn0.8A43o9pK4RfDQW5qmInOAGyQKE3TVq55iTuCvfTQzo8",
"tokenType": "bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "admin"
}
],
"details": null,
"authenticated": true,
"principal": "admin",
"credentials": "N/A",
"name": "admin"
},
"clientOnly": false,
"oauth2Request": {
"clientId": "root",
"scope": [
"all"
],
"requestParameters": {
"client_id": "root"
},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"refreshTokenRequest": null,
"grantType": null
},
"principal": "admin",
"credentials": "",
"name": "admin"
}
添加配置开启基于方法的权限校验:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(999)//将这里设置为最后执行,Order值越小优先级越高
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
在UserController中添加需要admin权限的接口:
@RestController
@RequestMapping("/user")
public class UserController {
@PreAuthorize("hasAuthority('admin')")
@GetMapping("/auth/admin")
public Object adminAuth() {
return "你具有访问的权限!";
}
}
访问接口:http://localhost:9002/user/auth/admin
使用admin
权限的帐号的账号访问 ,账号admin 密码 123
使用没有admin
权限的帐号的账号访问,账号user 密码 123
https://gitee.com/senhelpa-vivo/oauth2-sso.git