一、SSO基本介绍
SSO(Single Sign-On,单点登录)是身份管理中的一部分。SSO的一种较为通俗的定义是:SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
相关技术主要有:spring security ,shiro
二、spring security 基本实现
1.引入pom依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
2.添加配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Configuration @EnableDiscoveryClient @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private Logger logger = LoggerFactory.getLogger(ActivitiConfig.class);
@Override @Autowired public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService()); } @Bean public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = { {"salaboy", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam","GROUP_otherTeam"},
{"ryandawsonuk", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"erdemedeiros", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"}, {"admin", "password", "ROLE_ACTIVITI_ADMIN"}, }; for (String[] user : usersGroupsAndRoles) { List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]"); inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()))); }
return inMemoryUserDetailsManager; }
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .anyRequest() .authenticated() .and() .httpBasic();
}
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
}
|
启动测试:
三、spring security oauth2
1.认证服务
(1).引入pom依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
|
(2).认证方式
授权码模式:authorization_code
客户端模式:client_credentials
简化模式: implicit
密码模式: password
(3).认证服务访问路径
/oauth/authorize:授权端点。
/oauth/token:令牌端点。
/oauth/confirm_access:用户确认授权提交端点。
/oauth/error:授权服务错误信息端点。
/oauth/check_token:用于资源服务访问的令牌解析端点。
/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话
(4).认证服务配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private TokenStore tokenStore; @Autowired private UserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private BCryptPasswordEncoder passwordEncoder;
@Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()"). checkTokenAccess("permitAll()"). allowFormAuthenticationForClients(); }
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("password"). authorizedGrantTypes("authorization_code","client_credentials","implicit","password", "refresh_token"). accessTokenValiditySeconds(24*60*60*30*12). refreshTokenValiditySeconds(24*60*60*30*12*10). resourceIds("rid"). scopes("all"). secret(passwordEncoder.encode("123")) .autoApprove(false) .redirectUris("http://www.baidu.com"); }
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } }
|
(5).web安全配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| @Configuration @EnableWebSecurity @Slf4j public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserAuthenticationSuccessHandler userAuthenticationSuccessHandler;
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()); }
@Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); }
@Bean @ConditionalOnMissingBean(UserDetailsService.class) @Override protected UserDetailsService userDetailsService() { InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); String[][] usersGroupsAndRoles = { {"salaboy", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam", "GROUP_otherTeam"},
{"ryandawsonuk", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"erdemedeiros", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"}, {"admin", "password", "ROLE_ACTIVITI_ADMIN"}, }; for (String[] user : usersGroupsAndRoles) { List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); log.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]"); inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()))); }
return inMemoryUserDetailsManager; }
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers("/oauth/**") .permitAll().and().httpBasic().and() .csrf().disable(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
}
|
(6).请求方式
授权码模式:
客户端获取code值
/oauth/authorize?client_id=password&response_type=code&scope=all&redirect_uri=http://www.baidu.com
client_id:客户端准入标识。
response_type:授权码模式固定为code。
scope:客户端权限。
redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)
密码模式:
客户端模式:
简化模式:
浏览器访问:http://localhost:9006/oauth/authorize?client_id=password&response_type=token&scope=all&redirect_uri=http://www.baidu.com
2.资源服务
(1).引入pom依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
|
(2).资源服务配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Configuration @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId("rid"). .stateless(true); }
@Override public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() .antMatchers("/**/current/get","/**/oauth/**","/swagger-ui.html","/doc.html","/**/v2/api-docs/**","/error","/**/swagger-resources/**","/webjars/**") .permitAll() .antMatchers("/**") .hasAnyRole("ADMIN") .anyRequest() .authenticated(); }
}
|
四、oauth2集成redis
1.引入pom依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
|
2.yml 添加配置
1 2 3 4 5 6 7 8 9 10 11
| spring: redis: host: 192.168.42.146 port: 6379 database: 0 jedis: pool: max-active: 8 max-wait: -1 max-idle: 500 min-idle: 0
|
3.修改认证配置
1 2 3 4 5 6 7 8 9
| @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore tokenStore() { return new RedisTokenStore(redisConnectionFactory); }
|
效果如下:
五、oauth2集成jwt
1.认证服务
(1).引入pom依赖
1 2 3 4
| <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency>
|
(2).生成密钥
keytool -genkey -alias youlai -keyalg RSA -keypass 000000 -keystore youlai.jks -storepass 00000
-genkey:生成密钥
-alias:别名
-keyalg:密钥算法
-keypass:密钥口令
-keystore:生成密钥库的存储路径和名称
-storepass:密钥库口令
将生成的youlai.jks添加到resource下
(3).修改配置文件
1 2 3 4 5 6
| encrypt: key-store: location: classpath:youlai.jks secret: 000000 alias: youlai password: 0000
|
(4).修改认证服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Autowired @Autowired private KeyProperties keyProperties; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain=new TokenEnhancerChain(); final List<TokenEnhancer> objects = Arrays.asList(jwtAccessTokenConverter(),consumerTokenEnhancer()); enhancerChain.setTokenEnhancers(objects); endpoints.tokenEnhancer(enhancerChain) .accessTokenConverter(jwtAccessTokenConverter()) .authenticationManager(authenticationManager) .tokenStore(tokenStore) .userDetailsService(userDetailsService) .reuseRefreshTokens(false); } public ConsumerTokenEnhancer consumerTokenEnhancer(){ return new ConsumerTokenEnhancer(expiraTime,redisConnectionFactory); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); }
@Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setKeyPair(keyPair()); return converter; }
@Bean public KeyPair keyPair(){ return (new KeyStoreKeyFactory(this.keyProperties.getKeyStore().getLocation(), this.keyProperties.getKeyStore().getSecret().toCharArray())).getKeyPair(this.keyProperties.getKeyStore().getAlias(), this.keyProperties.getKeyStore().getPassword().toCharArray());
}
|
2.资源服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Autowired private TokenStore tokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId("rid").tokenStore(tokenStore) .stateless(true); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); Resource resource = new ClassPathResource("publicKey.txt"); String publicKey; try { publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); } catch (IOException e) { throw new RuntimeException(e); } converter.setVerifierKey(publicKey); return converter; }
|