我正在尝试让我的授权服务器生成一个JWT访问令牌,其中包含一些自定义声明。
我正在使用以下依赖项:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
授权服务器是这样配置的:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenServices(defaultTokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.accessTokenConverter(jwtAccessTokenConverter())
.userDetailsService(userDetailsService);
endpoints
.pathMapping("/oauth/token", RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN);
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}
@Bean
@Primary
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getSslKeystoreFilename()), jwtProperties.getSslKeystorePassword().toCharArray()).getKeyPair(jwtProperties.getSslKeyPair()));
return jwtAccessTokenConverter;
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
它使用类:
class CustomTokenEnhancer implements TokenEnhancer {
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
// Add user information to the token
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
info.put("organization", authentication.getName());
DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
return customAccessToken;
}
}
@Configuration
class CustomOauth2RequestFactory extends DefaultOAuth2RequestFactory {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserDetailsService userDetailsService;
public CustomOauth2RequestFactory(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}
@Override
public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
if (requestParameters.get("grant_type").equals("refresh_token")) {
OAuth2Authentication authentication = tokenStore
.readAuthenticationForRefreshToken(tokenStore.readRefreshToken(requestParameters.get("refresh_token")));
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(authentication.getName(), null,
userDetailsService.loadUserByUsername(authentication.getName()).getAuthorities()));
}
return super.createTokenRequest(requestParameters, authenticatedClient);
}
}
@Component
class CustomAccessTokenConverter extends JwtAccessTokenConverter {
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
}
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
info.put("organization", authentication.getName());
DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
return super.enhance(customAccessToken, authentication);
}
}
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())
与调试器一起运行时,不会调用这两个增强器重写。
要使用OAuth2、JWT和额外声明构建Spring Boot服务器,我们应该:
1)向项目添加依赖项:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
2)添加Web安全配置(以发布authenticationManager
bean-将在下一步中使用),例如:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> AuthUser.with()
.username(username)
.password("{noop}" + username)
.email(username + "@mail.com")
.authority(AuthUser.Role.values()[ThreadLocalRandom.current().nextInt(2)])
.build()
);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
@Value
@EqualsAndHashCode(callSuper = false)
public class AuthUser extends User {
private String email;
@Builder(builderMethodName = "with")
public AuthUser(final String username, final String password, @Singular final Collection<? extends GrantedAuthority> authorities, final String email) {
super(username, password, authorities);
this.email = email;
}
public enum Role implements GrantedAuthority {
USER, ADMIN;
@Override
public String getAuthority() {
return this.name();
}
}
}
@Configuration
@EnableAuthorizationServer
@EnableResourceServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
public static final String TOKEN_KEY = "abracadabra";
private final AuthenticationManager authenticationManager;
public AuthServerConfig(final AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void configure(ClientDetailsServiceConfigurer clientDetailsService) throws Exception {
clientDetailsService.inMemory()
.withClient("client")
.secret("{noop}")
.scopes("*")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(60 * 2) // 2 min
.refreshTokenValiditySeconds(60 * 60); // 60 min
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain chain = new TokenEnhancerChain();
chain.setTokenEnhancers(List.of(tokenEnhancer(), tokenConverter()));
endpoints
.tokenStore(tokenStore())
.reuseRefreshTokens(false)
.tokenEnhancer(chain)
.authenticationManager(authenticationManager);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(tokenConverter());
}
@Bean
public JwtAccessTokenConverter tokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(TOKEN_KEY);
converter.setAccessTokenConverter(authExtractor());
return converter;
}
private TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
if (authentication != null && authentication.getPrincipal() instanceof AuthUser) {
AuthUser authUser = (AuthUser) authentication.getPrincipal();
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("user_email", authUser.getEmail());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
}
return accessToken;
};
}
@Bean
public DefaultAccessTokenConverter authExtractor() {
return new DefaultAccessTokenConverter() {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
}
};
}
}
这里实现了一个简单的ClientDetailsService
。它只包含一个客户端,该客户端具有'client'名称、空白密码和授予的类型“password”和“refresh_token”。它使我们有可能创建一个新的访问令牌并刷新它。(要使用许多类型的客户端或在其他场景中工作,您必须实现ClientDetailsService
的更复杂的、可能是持久的变体。)
授权endpoint使用TokenEnhancerChain
配置,它包含TokenEnhancer
和TokenConverter
。在这个序列中添加它们是很重要的。第一个使用附加声明(在本例中为用户电子邮件)增强访问令牌。第二个创建一个JWT令牌。使用简单的JWTTokenStore
、我们的TokenEnhancerChain
和AuthenticationManager
设置的endpoint
。
jWTTokenStore
注意事项-如果您决定实现存储的持久性变体,可以在这里找到更多信息。
这里的最后一点是authextractor
,它使我们可以从传入请求的JWT令牌中提取声明。
然后所有的事情都设置好了,我们可以请求我们的服务器获得一个访问令牌:
curl -i \
--user client: \
-H "Content-Type: application/x-www-form-urlencoded" \
-X POST \
-d "grant_type=password&username=user&password=user&scope=*" \
http://localhost:8080/oauth/token
回复:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2VtYWlsIjoidXNlckBtYWlsLmNvbSIsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyIqIl0sImV4cCI6MTU0Nzc2NDIzOCwiYXV0aG9yaXRpZXMiOlsiQURNSU4iXSwianRpIjoiYzk1YzkzYTAtMThmOC00OGZjLWEzZGUtNWVmY2Y1YWIxMGE5IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RWSGMC0w8tNafT28i2GLTnPnIiXfAlCdydEsNNZK-Lw",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2VtYWlsIjoidXNlckBtYWlsLmNvbSIsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyIqIl0sImF0aSI6ImM5NWM5M2EwLTE4ZjgtNDhmYy1hM2RlLTVlZmNmNWFiMTBhOSIsImV4cCI6MTU0Nzc2NzcxOCwiYXV0aG9yaXRpZXMiOlsiQURNSU4iXSwianRpIjoiZDRhNGU2ZjUtNDY2Mi00NGZkLWI0ZDgtZWE5OWRkMDJkYWI2IiwiY2xpZW50X2lkIjoiY2xpZW50In0.m7XvxwuPiTnPaQXAptLfi3CxN3imfQCVKyjmMCIPAVM",
"expires_in": 119,
"scope": "*",
"user_email": "user@mail.com",
"jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9"
}
{
"user_email": "user@mail.com",
"user_name": "user",
"scope": [
"*"
],
"exp": 1547764238,
"authorities": [
"ADMIN"
],
"jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9",
"client_id": "client"
}
@RestController
public class DemoController {
@GetMapping("/demo")
public Map demo(OAuth2Authentication auth) {
var details = (OAuth2AuthenticationDetails) auth.getDetails();
//noinspection unchecked
var decodedDetails = (Map<String, Object>) details.getDecodedDetails();
return Map.of(
"name", decodedDetails.get("user_name"),
"email", decodedDetails.get("user_email"),
"roles", decodedDetails.get("authorities")
);
}
}
相关信息:
我创建了一个公共日志back-common.xml。我想在另一个文件logback.spring.xml中使用这个文件。请帮助我如何有效地使用它。 截至目前,应用程序正在启动,但不会在控制台中打印日志,并且日志不会填充到日志文件中。请帮忙。不要将其标记为重复,因为我几乎尝试了所有内容,并且我已经为此投入了2天。与此相关的其他问题没有附上有效的答案。 logback-spring.xml logba
JWT令牌的大多数示例都使用clj时间,而现在不推荐使用clj时间,而是使用本机java。时间我正试图与buddy一起使用java time对令牌进行签名/验证,但我一直在尝试将exp声明传递给我的令牌。以下是我所拥有的一个例子: 当我测试是否可以取消令牌签名时 我得到以下错误: 柴郡执行错误(JsonGenerationException)。生成/生成(generate.clj:152)。无法对
问题内容: 在Java中,你可以在一个文件中定义多个顶级类,条件是其中最多一个是公共的(请参见JLS§7.6)。参见以下示例。 是否有此技术整洁名(类似于)? JLS表示系统可能会强制执行这些二级类不能为的限制,例如,它们不能被视为程序包专用。这真的在Java实现之间有所改变吗? 例如,PublicClass.java: 问题答案: 对于这种技术,我建议的名称(在一个源文件中包括多个顶级类)将是“
问题内容: 我正在使用dgrijalva / jwt-go /软件包。 我想从令牌中提取有效载荷,但是我找不到方法。 示例(摘自:https : //jwt.io/): 对于编码: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOixMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydhVrF7H
我正在使用微软图形SDK为我的应用程序(不是用户)获取访问令牌,以便从共享点读取。我一直在关注这个文档,并发布了这个SO问题。链接的SO中的代码是相同的。我能够添加应用程序权限以及在Azure门户中授予它们(通过按下按钮)。问题是,返回使用的令牌中不包含任何角色/scp声明。因此,当使用令牌时,我收到“令牌中需要存在scp或角色声明”消息。 可以肯定的是,在获取访问令牌时,我传递的范围的唯一值是:
我正在考虑将Auth0用于我的API和web应用程序,并进行查询。生成Jwt令牌时,我希望包含一些仅存在于我的用户数据库中的自定义用户声明。这是可能的,还是所有声明都需要作为Auth0中的预定义属性存在。 我有自己的用户数据库,因为我需要在那里存储一些动态和复杂的用户权限。我意识到一个选择是不将这些权限存储在令牌中,我可以有一个单独的api来获取它们,但是为了性能和简单性,我宁愿将它们包装到Jwt