Spring OAuth2 - 授权服务器 - 在客户端区分用户
Spring OAuth2 - Authorization Server - Differentiate users on clients
我的申请卡住了。这听起来很简单:我在我的 OAuth AuthorizationServer 上注册了两个客户端和两个用户。用户 alpha 可以访问这两个应用程序("androidapp" 和 "angularapp"),但是用户 beta 只能访问其中一个应用程序(仅 "angularapp")。我如何区分 "androidapp" 应用程序的用户并阻止测试版?
这是我的 AuthServer 代码:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired private DataSource dataSource;
@Autowired private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("angularapp")
.secret(passwordEncoder.encode("12345"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000)
.and()
.withClient("androidapp")
.secret(passwordEncoder.encode("67890"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
return jwt;
}
}
提前感谢您的回答。
我的解决方案:
When the loadClientByClientId
method is executed, the Principal
object
stored in SecurityContext
doesn't yet exist, but it does when the
loadUserByUsername
method is executed with a slight observation: The
Principal
object at this point contains the client_id
, not the
username
, resulting in customizing the UserDetailsService
object
instead of ClientsDetailsService
. Then, with a relational entity (JPA)
I joined the client_id
with the username
giving the expected result.
所以,UserDetailsService
实现的代码是:
@Service
public class UsuarioService implements IUsuarioService, UserDetailsService{
private Logger logger = LoggerFactory.getLogger(UsuarioService.class);
@Autowired
private IUsuarioDao usuarioDao;
@Override
@Transactional(readOnly=true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario usuario = usuarioDao.findByUsername(username);
if( usuario == null ) {
logger.error("Login error: Username not found in storage");
throw new UsernameNotFoundException("Login error: Username not found in storage");
}
List<GrantedAuthority> authorities = usuario.getRoles().stream().map( role -> new SimpleGrantedAuthority( role.getNombre() )).collect(Collectors.toList());
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String applicationID = "";
if (principal instanceof UserDetails) {
applicationID = ((UserDetails)principal).getUsername();
} else {
applicationID = principal.toString();
}
logger.info("Application: {} ", applicationID);
if( applicationID == null || applicationID.isEmpty() ) {
logger.error("Application ID can't be empty");
throw new InsufficientAuthenticationException("Application ID can't be empty");
}
OAuthClientDetails app = findApplicationByUsername( usuario.getClientes(), applicationID);
if( app == null ) {
logger.error("Unauthorized user for application {}", applicationID);
throw new UnapprovedClientAuthenticationException("Unauthorized user for application " + applicationID);
}
return new User(username, usuario.getPassword(), usuario.getEnabled(), true, true, true, authorities);
}
private OAuthClientDetails findApplicationByUsername( final List<OAuthClientDetails> list, final String clientID ){
return list.stream().filter( p -> p.getClientId().equals(clientID) ).findAny().orElse(null); } }
AuthorizationServer 配置为:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired DataSource dataSource;
@Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
return jwt;
}
}
非常感谢您的帮助和想法。
我以前解决的方法是创建ResourceOwnerPasswordTokenGranter
的子类并重写这个方法:
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
如果您从 Spring 来源复制原始方法,您可以在某个时候访问 client_id (client.getClientId()
) 和用户 (userAuth.getPrincipal()
).
如果用户的角色与客户端不匹配,我抛出一个InsufficientAuthenticationException
以避免用户可以登录。
如果 Spring 安全部门能有某种回调以避免必须复制部分代码才能执行此操作,那就太好了。我为此打开了https://github.com/spring-projects/spring-security-oauth/issues/791。
我的申请卡住了。这听起来很简单:我在我的 OAuth AuthorizationServer 上注册了两个客户端和两个用户。用户 alpha 可以访问这两个应用程序("androidapp" 和 "angularapp"),但是用户 beta 只能访问其中一个应用程序(仅 "angularapp")。我如何区分 "androidapp" 应用程序的用户并阻止测试版?
这是我的 AuthServer 代码:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired private DataSource dataSource;
@Autowired private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("angularapp")
.secret(passwordEncoder.encode("12345"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000)
.and()
.withClient("androidapp")
.secret(passwordEncoder.encode("67890"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
return jwt;
}
}
提前感谢您的回答。
我的解决方案:
When the
loadClientByClientId
method is executed, thePrincipal
object stored inSecurityContext
doesn't yet exist, but it does when theloadUserByUsername
method is executed with a slight observation: ThePrincipal
object at this point contains theclient_id
, not theusername
, resulting in customizing theUserDetailsService
object instead ofClientsDetailsService
. Then, with a relational entity (JPA) I joined theclient_id
with theusername
giving the expected result.
所以,UserDetailsService
实现的代码是:
@Service
public class UsuarioService implements IUsuarioService, UserDetailsService{
private Logger logger = LoggerFactory.getLogger(UsuarioService.class);
@Autowired
private IUsuarioDao usuarioDao;
@Override
@Transactional(readOnly=true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario usuario = usuarioDao.findByUsername(username);
if( usuario == null ) {
logger.error("Login error: Username not found in storage");
throw new UsernameNotFoundException("Login error: Username not found in storage");
}
List<GrantedAuthority> authorities = usuario.getRoles().stream().map( role -> new SimpleGrantedAuthority( role.getNombre() )).collect(Collectors.toList());
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String applicationID = "";
if (principal instanceof UserDetails) {
applicationID = ((UserDetails)principal).getUsername();
} else {
applicationID = principal.toString();
}
logger.info("Application: {} ", applicationID);
if( applicationID == null || applicationID.isEmpty() ) {
logger.error("Application ID can't be empty");
throw new InsufficientAuthenticationException("Application ID can't be empty");
}
OAuthClientDetails app = findApplicationByUsername( usuario.getClientes(), applicationID);
if( app == null ) {
logger.error("Unauthorized user for application {}", applicationID);
throw new UnapprovedClientAuthenticationException("Unauthorized user for application " + applicationID);
}
return new User(username, usuario.getPassword(), usuario.getEnabled(), true, true, true, authorities);
}
private OAuthClientDetails findApplicationByUsername( final List<OAuthClientDetails> list, final String clientID ){
return list.stream().filter( p -> p.getClientId().equals(clientID) ).findAny().orElse(null); } }
AuthorizationServer 配置为:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired DataSource dataSource;
@Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
return jwt;
}
}
非常感谢您的帮助和想法。
我以前解决的方法是创建ResourceOwnerPasswordTokenGranter
的子类并重写这个方法:
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
如果您从 Spring 来源复制原始方法,您可以在某个时候访问 client_id (client.getClientId()
) 和用户 (userAuth.getPrincipal()
).
如果用户的角色与客户端不匹配,我抛出一个InsufficientAuthenticationException
以避免用户可以登录。
如果 Spring 安全部门能有某种回调以避免必须复制部分代码才能执行此操作,那就太好了。我为此打开了https://github.com/spring-projects/spring-security-oauth/issues/791。