这里分为两部分,第一部分是授权中心的搭建,第二部分是其他微服务搭配资源中心的鉴权,好了,话不多说,开撸。
项目结构如图:
MyPrincipal
和MyUserDetails
是为了自定义用户的信息,可以方便http://localhost:1006/auth/user?access_token=41eb0a06-0145-4bec-97f7-b80719b616d2
这一类获取用户信息的接口扩展一些有用的信息。
OAuth2ServerConfig
是SpringCloud Oauth2的配置.
SecurityConfiguration
是Spring security的配置
pom.xml
<dependencies>
<dependency>
<groupId>com.td</groupId>
<artifactId>common-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 注意是starter,自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 不是starter,手动配置 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>td</groupId>
<artifactId>myjdbc</artifactId>
<version>4.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
common-server
是我实体类和工具类的公共包
myjdbc
是我自己写的jdbc的轮子。
OAuth2ServerConfig
@Configuration
public class OAuth2ServerConfig {
private static final String DEMO_RESOURCE_ID = "order";
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/v2/api-docs/**","/role/**","/rule/**","/users/**","/testFeign/**").permitAll()//配置不需要权限校验
.anyRequest().authenticated()
.and()
.httpBasic()
;
}
}
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
UserDetailServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Bean // 声明 ClientDetails实现
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// password 方案一:明文存储,用于测试,不能用于生产
// String finalSecret = "123456";
// password 方案二:用 BCrypt 对密码编码
// String finalSecret = new BCryptPasswordEncoder().encode("123456");
// password 方案三:支持多种编码,通过密码的前缀区分编码方式
// String finalSecret = "{bcrypt}"+new BCryptPasswordEncoder().encode("123456");
// //配置两个客户端,一个用于password认证一个用于client认证
// clients.inMemory().withClient("client_1")
// .resourceIds(DEMO_RESOURCE_ID)
// .authorizedGrantTypes("client_credentials", "refresh_token")
// .scopes("select")
// .authorities("oauth2")
// .secret(finalSecret)
// .and().withClient("client_2")
// .resourceIds(new String[]{"order","eurekaDemo1"})
// .authorizedGrantTypes("password", "refresh_token")
// .scopes("select")
// .authorities("oauth2")
// .secret(finalSecret);
clients.withClientDetails(clientDetails());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.userDetailsService(userDetailsService);//若无,refresh_token会有UserDetailsService is required错误
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
//允许表单认证
oauthServer.allowFormAuthenticationForClients();
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
}
接着是数据库jdbc的实现
UserDetailServiceImpl
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
SysUserDao sysUserDao;
@Autowired
SysUserAuthDao authDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//spring加密
// PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String lowercaseLogin = s.toLowerCase();
//获取用户
SysUser sysUser = sysUserDao.getByUserName(lowercaseLogin);
if (sysUser == null) {
throw new UsernameNotFoundException("用户:" + lowercaseLogin + "不存在!");
}
List<SysUserAuth> userAuthList = authDao.findAll(sysUser.getId()+"");
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
userAuthList.forEach((sysUserAuth) -> {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(sysUserAuth.getSysRoleCode());
grantedAuthorities.add(grantedAuthority);
});
//返回一个SpringSecurity需要的用户对象
UserDetails userDetails = new org.springframework.security.core.userdetails.User(
sysUser.getUsername(),
// passwordEncoder.encode(user.getPassword()),
sysUser.getPassword(),
grantedAuthorities);
UserDetails myuser = new MyPrincipal( sysUser.getUsername(),sysUser.getPassword(),grantedAuthorities);
return myuser;
// return userDetails;
}
}
注意grantedAuthorities
这个权限的集合和最终的返回值UserDetails
。最后的MyPrincipal
这个对象就是我自定义的那个user对象。
SecurityConfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder());
// }
//这部分是基于内存的
// @Bean
// @Override
// protected UserDetailsService userDetailsService(){
// BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
password 方案一:明文存储,用于测试,不能用于生产
String finalPassword = "123456";
password 方案二:用 BCrypt 对密码编码
String finalPassword = bCryptPasswordEncoder.encode("123456");
// // password 方案三:支持多种编码,通过密码的前缀区分编码方式
// String finalPassword = "{bcrypt}"+bCryptPasswordEncoder.encode("123456");
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("admin").password(finalPassword).authorities("ADMIN").roles("ADMIN","USER").build());
// manager.createUser(User.withUsername("user_1").password(finalPassword).authorities("USER").roles("USER").build());
// manager.createUser(User.withUsername("user_2").password(finalPassword).authorities("USER").roles("USER").build());
// return manager;
// }
@Autowired
private UserDetailServiceImpl userDetailService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailService);
}
/**
* springboot2.0 删除了原来的 plainTextPasswordEncoder
* https://docs.spring.io/spring-security/site/docs/5.0.4.RELEASE/reference/htmlsingle/#10.3.2 DelegatingPasswordEncoder
*
*/
// password 方案一:明文存储,用于测试,不能用于生产
// @Bean
// PasswordEncoder passwordEncoder(){
// return NoOpPasswordEncoder.getInstance();
// }
// password 方案二:用 BCrypt 对密码编码
// @Bean
// PasswordEncoder passwordEncoder(){
// return new BCryptPasswordEncoder();
// }
// password 方案三:支持多种编码,通过密码的前缀区分编码方式,推荐
@Bean
PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//
// /**
// * 这一步的配置是必不可少的,否则SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
// */
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers().anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/**").permitAll();
// @formatter:on
}
}