认证授权
OAuth 2.0 Authorization Server
- 新建项目
- Artifact: auth-server
- Dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.0.M3</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>7.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
- resources/application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/first_db?useSSL=false
username: root
password: jjjjjj
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
continue-on-error: true
jpa:
show-sql: true
hibernate:
ddl-auto: update
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:public.txt
- resources/schema.sql
create table if not exists oauth_client_details (
client_id VARCHAR(255) PRIMARY KEY,
resource_ids VARCHAR(255),
client_secret VARCHAR(255),
scope VARCHAR(255),
authorized_grant_types VARCHAR(255),
web_server_redirect_uri VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove varchar(255)
);
- resources/data.sql
INSERT INTO oauth_client_details (client_id, client_secret, scope, authorized_grant_types, autoapprove)
values ('postman',
'$2a$10$KCroi.THbmXdKXOgBud1zOzdmHrfpNxSytd/o5ZQLhCTzFXib1p66',
'any',
'password,authorization_code,refresh_token',
true);
INSERT INTO oauth\_client\_details (client_id, client_secret, authorized_grant_types, autoapprove)
values ('app',
'$2a$10$1vUvCP9fzPeXIRzoOaPROuIRvq2nrh7iauWLIa371qZSmaP0p6ave',
'any',
'implicit',
true);
- resources/keystore.jks
keytool -list -rfc --keystore keystore.jks | openssl x509 -inform pem -pubkey -noout
- resources/public.txt
@Bean
CommandLineRunner publicKey(KeyPair keyPair) {
return args -> {
System.out.println(Base64.encodeBase64String(keyPair.getPublic().getEncoded()));
};
}
- entity/SysAuthority
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class SysAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String value;
public SysAuthority(String name, String value) {
this.name = name;
this.value = value;
}
}
- entity/SysRole
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class SysRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(targetEntity = SysAuthority.class)
private Set<SysAuthority> authorities;
public SysRole(String name, Set<SysAuthority> authorities) {
this.name = name;
this.authorities = authorities;
}
}
- entity/SysUser
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class SysUser implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
@NotNull
private String username;
private String password;
private String realName;
private Boolean enable;
@ManyToMany(targetEntity = SysRole.class, fetch = FetchType.EAGER)
private Set<SysRole> roles;
public SysUser(String username, String password, String realName, Boolean enable, Set<SysRole> roles) {
this.username = username;
this.password = password;
this.realName = realName;
this.enable = enable;
this.roles = roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
roles.forEach(role -> {
role.getAuthorities().forEach(authority -> {
authorities.add(new SimpleGrantedAuthority(authority.getValue()));
});
});
return authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enable;
}
}
- repository/SysAuthorityRepository
public interface SysAuthorityRepository extends JpaRepository<SysAuthority, Long> {
}
- repository/SysRoleRepository
public interface SysRoleRepository extends JpaRepository<SysRole, Long> {
}
- repository/SysUserRepository
public interface SysUserRepository extends JpaRepository<SysUser, Long> {
Optional<SysUser> findByUsername(String username);
}
- AuthServerApplication
@SpringBootApplication
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
//@Bean
CommandLineRunner init(SysUserRepository sysUserRepository,
SysRoleRepository sysRoleRepository,
SysAuthorityRepository sysAuthorityRepository,
PasswordEncoder passwordEncoder){
return args -> {
SysAuthority authority1 = sysAuthorityRepository.save(new SysAuthority("userCan1","userCan1"));
SysAuthority authority2 = sysAuthorityRepository.save(new SysAuthority("userCan2","userCan2"));
SysAuthority authority3 = sysAuthorityRepository.save(new SysAuthority("adminCan1","adminCan1"));
SysAuthority authority4 = sysAuthorityRepository.save(new SysAuthority("adminCan2","adminCan2"));
SysRole role1 = sysRoleRepository.save(new SysRole("普通用户",
Stream.of(authority1,authority2).collect(Collectors.toSet())));
SysRole role2 = sysRoleRepository.save(new SysRole("管理员",
Stream.of(authority1,authority2,authority3,authority4).collect(Collectors.toSet())));
SysUser user1 = sysUserRepository.save(new SysUser("wyf",
passwordEncoder.encode("111111"),
"wangyunfei",
true,
Stream.of(role1).collect(Collectors.toSet())));
SysUser user2 = sysUserRepository.save(new SysUser("admin",
passwordEncoder.encode("admin"),
"administrator",
true,
Stream.of(role2).collect(Collectors.toSet())));
};
}
}
- 安全配置
- security/CusotmUserDetailsService
public class CusotmUserDetailsService implements UserDetailsService {
SysUserRepository sysUserRepository;
public CusotmUserDetailsService(SysUserRepository sysUserRepository) {
this.sysUserRepository = sysUserRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<SysUser> sysUserOptional = sysUserRepository.findByUsername(username);
return sysUserOptional
.orElseThrow(() -> new UsernameNotFoundException("Username not found!"));
}
}
- config/WebSecurityConfig
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SysUserRepository sysUserRepository;
@Bean
protected UserDetailsService userDetailsService() {
return new CusotmUserDetailsService(sysUserRepository);
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated();
}
}
- JWT 配置
- application.yml
security:
oauth2:
authorization:
jwt:
key-store: classpath:keystore.jks
key-store-password: pass1234
key-alias: wisely
key-password: pass1234
tokenKeyAccess: permitAll()
checkTokenAccess: isAuthenticated()
realm: wisely
- 授权服务配置
- config/AuthServerConfig
@EnableAuthorizationServer
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final DataSource dataSource;
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final TokenStore tokenStore;
private final AccessTokenConverter accessTokenConverter;
private final AuthorizationServerProperties properties;
public AuthServerConfig(PasswordEncoder passwordEncoder,
DataSource dataSource,
AuthenticationManager authenticationManager,
UserDetailsService userDetailsService,
TokenStore tokenStore,
AccessTokenConverter accessTokenConverter,
AuthorizationServerProperties properties) {
this.passwordEncoder = passwordEncoder;
this.dataSource = dataSource;
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.tokenStore = tokenStore;
this.accessTokenConverter = accessTokenConverter;
this.properties = properties;
}
/**
* 配置客户端
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(this.dataSource)
.passwordEncoder(passwordEncoder);
//
// JdbcClientDetailsService detailsService = new JdbcClientDetailsService(this.dataSource);
// detailsService.setPasswordEncoder(passwordEncoder);
// clients.withClientDetails(detailsService);
// clients
// .inMemory()
// .withClient("postman")
// .secret(passwordEncoder.encode("postman"))
// .scopes("any")
// .authorizedGrantTypes("password", "authorization\_code", "refresh\_token")
// .and()
// .withClient("app")
// .secret(passwordEncoder.encode("app"))
// .scopes("any")
// .authorizedGrantTypes("password", "authorization\_code", "refresh\_token");
}
/**
* 配置端点非安全属性
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(this.accessTokenConverter);
endpoints.tokenStore(this.tokenStore);
endpoints.authenticationManager(this.authenticationManager);
endpoints.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess(this.properties.getCheckTokenAccess());
security.tokenKeyAccess(this.properties.getTokenKeyAccess());
security.realm(this.properties.getRealm());
}
}
- Authorization Server 的端点
- TokenEndpoint:/oauth/token,按照 OAuth 2.0 规范请求 Token。
- TokenKeyEndpoint:/oauth/token_key,提供 JWT 编码的 Token。
- CheckTokenEndpoint:/oauth/check_token,解码客户端的访问令牌用来检查和确认生成的 Token。
- AuthorizationEndpoint:/oauth/authorize,遵循 OAuth 2.0 规范的授权实现。
- 获取 Token
- 获取访问令牌
Basic Auth: postman postman
Post http://localhost:8080/oauth/token?grant_type=password&username=wyf&password=111111 - 检查 Token
Get http://localhost:8080/oauth/check_token - 刷新 Token
Post http://localhost:8080/oauth/token?grant_type=refresh_token&username=wyf&password=111111
OAuth 2.0 Resource Server
- 新建应用
- Artifact: resource-server
- Dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 公钥配置 application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath: public.txt
server:
port: 8082
- 获取资源服务权限
- config/WebSecurityConfig
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwt -> {
Collection<SimpleGrantedAuthority> authorities = ((Collection<String>) jwt.getClaims().get("authorities")).stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities);
});
}
}
- 测试控制器
- controller/SecurityController
@RestController
public class SecurityController {
@GetMapping("/userCan1")
@PreAuthorize("hasAuthority('userCan1')")
public Jwt userCan1(@AuthenticationPrincipal Jwt jwt){
return jwt;
}
@GetMapping("/userCan2")
@PreAuthorize("hasAuthority('userCan2')")
public Jwt userCan2(@AuthenticationPrincipal Jwt jwt){
return jwt;
}
@GetMapping("/adminCan1")
@PreAuthorize("hasAuthority('adminCan1')")
public Jwt adminCan1(@AuthenticationPrincipal Jwt jwt){
return jwt;
}
@GetMapping("/adminCan2")
@PreAuthorize("hasAuthority('adminCan2')")
public Jwt adminCan2(@AuthenticationPrincipal Jwt jwt){
return jwt;
}
}
- 验证
分别用 wyf 和 admin 请求token,复制 wyf 和 admin 的 access_token 请求 http://localhost:8082/adminCan1
Gateway
客户端调用
- 获取 Access Token
- 访问 Resource Server
微服务互调
- 为 resource-server 添加方法,用于远程调用
- 新建调用微服务
- 外部配置
- 开启支持
- Security 配置
- Feign 客户端
- 控制器
- 验证
来源:oschina
链接:https://my.oschina.net/beanho/blog/3167956