The AOuth2 Authorization server does not insert token in the given database - spring-boot

There is some configuration class in Authorization server:
#Configuration
public class AppConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.database.driverClassName}")
private String dbDriverClassName;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dbDriverClassName);
dataSource.setUrl(datasourceUrl);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
return dataSource;
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource());
}
}
and the another class:
#EnableWebSecurity
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AccountUserDetailsService accountUserDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new HashingClass();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return accountUserDetailsService;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/webjars/**", "/resources/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean())
.passwordEncoder(passwordEncoder());
}
}
and the third class is:
#EnableAuthorizationServer
#Configuration
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final AppConfig appConfig;
#Autowired
public AuthServerOAuth2Config(AuthenticationManager authenticationManager, AppConfig appConfig) {
this.authenticationManager = authenticationManager;
this.appConfig = appConfig;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(appConfig.dataSource());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/*
* Allow our tokens to be delivered from our token access point as well as for tokens
* to be validated from this point
*/
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(appConfig.tokenStore()); // Persist the tokens in the database
}
}
and the client application is like this:
#SpringBootApplication
#EnableOAuth2Sso
#RestController
#Configuration
public class SocialApplication extends WebSecurityConfigurerAdapter {
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
#RequestMapping("/user")
public Principal user(HttpServletResponse response, Principal principal) {
return principal;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**", "/index.html", "/getEmployees.jsp").permitAll()
.anyRequest().authenticated()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
return details;
}
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
}
and its application.yml file is:
server:
port: 8090
security:
basic:
enabled: false
oauth2:
client:
clientId: web
clientSecret: secret
accessTokenUri: http://localhost:8081/auth/oauth/token
userAuthorizationUri: http://localhost:8081/auth/oauth/authorize
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: http://localhost:8081/auth/user
logging:
level:
org.springframework.security: DEBUG
When I request http://localhost:8090/user (this rest is owned client) I redirected to Authorization server and I can login successfully after login again I am redirected to the client with a code, but client is not able to exchange access token with the given code. and the following exception is raised on the browser:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing
this as a fallback. Mon Dec 17 09:47:27 IRST 2018 There was an
unexpected error (type=Unauthorized, status=401). Authentication
Failed: Could not obtain access token
And I query from DB:
select * from oauth_access_token
or
select * from oauth_client_token
The two above tables are empty.
Where is wrong? I am really confused.
EDIT
The application.properties file of AOuthrization server:
server.port=8081
server.context-path=/auth
security.basic.enabled=false
# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
# Connection url for the database w/createDatabaseIfNotExist=true
spring.datasource.url = jdbc:oracle:thin:#192.168.192.129:1521:hamed
spring.database.driverClassName = oracle.jdbc.OracleDriver
#spring.jpa.database = MySQL
#spring.datasource.platform = mysql
# Database - data initialization
spring.jpa.generate-ddl = true
# Username and password
spring.datasource.username = test
spring.datasource.password = test
# ===============================
# = JPA / HIBERNATE
# ===============================
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager).
# Show or not log for each sql query
spring.jpa.show-sql = true
# Transactions
spring.jpa.open-in-view = false
# Hibernate ddl auto (create, create-drop, update): with "update" the database
# schema will be automatically updated accordingly to java entities found in
# the project
spring.jpa.hibernate.ddl-auto = none
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle12cDialect
and the its pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</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-web</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<version>12.1.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>r05</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>11.0</version>
</dependency>
</dependencies>
and the version of spring boot is:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/>
</parent>

I noticed you are using spring-boot and to me seems like you can clean up your code a little, for instance, all this DataSource config may not be necessary.
As for your problem, it could be related to the class AuthServerOAuth2Config where you are injecting AppConfig instead of DataSource and TokenStore, making this change can solve your problem.
Here you can find a sample auth service I did a while ago https://github.com/marcosbarbero/spring-security-authserver-sample

I could solve my problem by removing two lines of application.yml of client application.
These two lines are:
authenticationScheme: query
clientAuthenticationScheme: form
Why these two lines cause problem, I do not know.

Related

Spring Boot - connect resource server to Spring Session with an Redis server

For evaluation purposes we would like to use Spring Session in combination with an Embedded-Redis server.
When using Spring Boot 2.6.7 with JDK17 we start the Spring Session with an Embedded-Redis server. The server is started just after the 'UI' server. The code and config is given at the end.
The 'resource' server that is receiving the Spring Session token cannot connect to redis.
The resource server code is:
#SpringBootApplication
#RestController
public class ResourceApplication extends WebSecurityConfigurerAdapter {
private final static Logger logger = LoggerFactory.getLogger(ResourceApplication.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().authorizeRequests().anyRequest().authenticated();
}
#RequestMapping("/")
#CrossOrigin(origins = "*", maxAge = 3600, allowedHeaders = { "x-auth-token", "x-requested-with", "x-xsrf-token" })
public Message home() {
logger.info( "ResourceServer: home()");
return new Message("Hello World");
}
#Bean
HeaderHttpSessionStrategy sessionStrategy() {
return new HeaderHttpSessionStrategy();
}
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}
The properties are:
security.sessions=NEVER
spring.session.store-type=redis
spring.redis.host=localhost
spring.redis.port=6379
The dependencies are:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
When adding e.g. the spring-session-data-redis
The error message is:
An attempt was made to call a method that does not exist. The attempt
was made from the following location:
org.springframework.boot.autoconfigure.session.RedisSessionConfiguration$SpringBootRedisHttpSessionConfiguration.customize(RedisSessionConfiguration.java:80)
When adding the spring-sessions-data-redis dependency, gives the same error message.
Only for your information, starting up the Redis-embedded server goes like this:
#SpringBootApplication
#EnableWebSecurity
public class DemoApplication {
private Logger logger = LoggerFactory.getLogger(getClass());
private RedisServer redisServer;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostConstruct
public void startRedis() throws IOException {
try {
redisServer = RedisServer.builder().setting("maxheap 256Mb").port(6379).build();
redisServer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
#PreDestroy
public void stopRedis(){
redisServer.stop();
}
}
Dependencies:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
</dependency>
The resources:
spring.redis.host=localhost
spring.redis.password=
spring.redis.port=6379
spring.session.store-type=redis

How to use custom UserDetailService in Spring OAuth2 Resource Server?

I'm using Spring Boot (2.3.4.RELEASE) to implement a webservice acting as a OAuth2 resource server. So far I'm able to secure all endpoints and ensure that a valid token is present. In the next step I want to use Spring Method Security. The third step would be to populate custom user details (via UserDetailsService).
How to configure Spring Method Security properly?
I'm not able to enable Spring Method Security (correctly). I have entities saved in database and also set the permissions via MutableAclService. Creating new resource is no problem.
I get the following error on reading the entity
o.s.s.acls.AclPermissionEvaluator : Checking permission 'OWNER' for object 'org.springframework.security.acls.domain.ObjectIdentityImpl[Type: io.mvc.webserver.repository.entity.ProjectEntity; Identifier: my-second-project]'
o.s.s.acls.AclPermissionEvaluator : Returning false - no ACLs apply for this principal
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#120d62d, returned: -1
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter#429b9eb9, returned: 0
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter#65342bae, returned: 0
o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.security.access.AccessDeniedException: Zugriff verweigert
o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is not anonymous); delegating to AccessDeniedHandler
I use the following Expression:
#PreAuthorize("hasPermission(#projectKey, 'io.mvc.webserver.repository.entity.ProjectEntity', 'OWNER')")
ProjectEntity findByKey(String projectKey);
How to provide Custom User Details Service?
As far as I understand Spring Security sets the SecurityContext accordingly to the authenticated user (by OAuth2 JWT). I want to set a custom user object (principal) based on the identified user from the token. But just providing a Bean of type UserDetailsService does not seem to work. My UserDetailsService is never invoked...
Security configuration
#Configuration
#EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.httpBasic().disable()
.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests(authorize -> authorize
.antMatchers("/actuator/**").permitAll() // TODO: Enable basic auth for actuator
.anyRequest().authenticated()
)
.oauth2ResourceServer().jwt();
}
}
ACL configuration
#Configuration
public class AclConfiguration {
#Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
#Bean
public PermissionEvaluator permissionEvaluator(PermissionFactory permissionFactory, AclService aclService) {
AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService);
permissionEvaluator.setPermissionFactory(permissionFactory);
return permissionEvaluator;
}
#Bean
public PermissionFactory permissionFactory() {
return new DefaultPermissionFactory(MvcPermission.class);
}
#Bean
public MutableAclService aclService(LookupStrategy lookupStrategy, AclCache aclCache, AclRepository aclRepository) {
return new MongoDBMutableAclService(aclRepository, lookupStrategy, aclCache);
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
#Bean
public AclCache aclCache(PermissionGrantingStrategy permissionGrantingStrategy,
AclAuthorizationStrategy aclAuthorizationStrategy,
EhCacheFactoryBean ehCacheFactoryBean) {
return new EhCacheBasedAclCache(
ehCacheFactoryBean.getObject(),
permissionGrantingStrategy,
aclAuthorizationStrategy
);
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(ehCacheManagerFactoryBean.getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
EhCacheManagerFactoryBean cacheManagerFactory = new EhCacheManagerFactoryBean();
cacheManagerFactory.setShared(true);
return cacheManagerFactory;
}
#Bean
public LookupStrategy lookupStrategy(MongoTemplate mongoTemplate,
AclCache aclCache,
AclAuthorizationStrategy aclAuthorizationStrategy) {
return new BasicMongoLookupStrategy(
mongoTemplate,
aclCache,
aclAuthorizationStrategy,
new ConsoleAuditLogger()
);
}
}
dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
In your ResourceServerConfig class you should override configureGlobal and authenticationManagerBean methods, as well as providing passwordEncoderBean in order to invoke your userDeatailsService:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoderBean());
}
#Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
The variable userDetailsService in configureGlobal should hold a reference (through dependency injection #Autowird in your class) to your implementation of org.springframework.security.core.userdetails.UserDetailsService, in the implementation you should override the method loasUserByUsername to get the actual user in your database and pass the required values to UserDetails user, this user or Principal is what will be used in the authentication manager:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserFromDb> user = userRepository.findByUsername(username);
if (!user.isPresent()) {
throw new UsernameNotFoundException("User not found!");
}
return new MyUser(user.get());
}
The class MyUser should implement org.springframework.security.core.userdetails.UserDetails and pass the required values to MyUser as the example shows. How to pass the required values is up to you, here I passed the user from database and internally in the implementation I extracted whatever values are needed.
You should add the following line to the end of configure method
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
authenticationTokenFilter is of a type that implements OncePerRequestFilter, you should override the method doFilterInternal:
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
final String requestTokenHeader = httpServletRequest.getHeader("Authorization");//sometime it's lowercase: authorization
String username = getUserName(requestTokenHeader);
String jwtToken = getJwtToken(requestTokenHeader);
if (username != null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (isValidToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
Of course you should write the logic of getUserName, getJwtToken and isValidToken methods, which require understanding of JWT token and http headers...

#oauth2.hasScope in spring boot + keycloak

I want to use #oauth2.hasScope in spring boot + keycloak, get token with grant_type = client_credentials. but it's not working. this is my code, can u pls help me .
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.7.RELEASE</version>
</dependency>
This is my config
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
String jwkSetUri = "http://localhost:8080/auth/realms/microservice/protocol/openid-connect/certs";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
}
}
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return getOAuth2MethodSecurityExpressionHandler();
}
/**
*A security expression handler that can handle default method security expressions plus the set provided by OAuth2SecurityExpressionMethods using the variable oauth2 to access the methods. For example, the expression #oauth2.clientHasRole('ROLE_ADMIN') would invoke OAuth2SecurityExpressionMethods.clientHasRole(java.lang.String)
*By default the OAuth2ExpressionParser is used. If this is undesirable one can inject their own ExpressionParser using AbstractSecurityExpressionHandler.setExpressionParser(ExpressionParser).
*/
#Bean
public OAuth2MethodSecurityExpressionHandler getOAuth2MethodSecurityExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
This is my controller
hasAnyAuthority('SCOPE_phone') is ok, but #oauth2.hasScope('phone') or #oauth2.hasScope('SCOPE_phone') not working i receive 403 forbidden.
#RequestMapping(value = "/user", method = RequestMethod.GET)
#PreAuthorize("hasAnyAuthority('SCOPE_phone')")
public ResponseEntity<String> getUser() {
return ResponseEntity.ok("Hello User");
}
#RequestMapping(value = "/all-user", method = RequestMethod.GET)
#PreAuthorize("#oauth2.hasScope('phone')")
public ResponseEntity<String> getAllUser() {
return ResponseEntity.ok("Hello All User");
}
This is my token, i have phone scope.
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1MkRsOVNTMlREY0M5SkFtZmZ3ZE1BNjJkbFBreDlFMDdRSnhObF9sVDNJIn0.eyJleHAiOjE2MDM3ODQzNTMsImlhdCI6MTYwMzc4NDA1MywianRpIjoiY2Q2OWIzMDgtNDBmZi00YjJmLTljOWMtNjMyZjQxYTYyNzgwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21pY3Jvc2VydmljZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIxZGExM2RjMy0yNDQ1LTRlZTQtYjFhNS0zNjc2YzYyMjY4OTciLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJtb2JpbGVyZXRhaWwiLCJzZXNzaW9uX3N0YXRlIjoiMmIxMzRlN2MtNDZiMS00MGNmLWIyMmYtODA3MDcyYWFjMGU0IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsibW9iaWxlcmV0YWlsIjp7InJvbGVzIjpbInVtYV9wcm90ZWN0aW9uIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InBob25lIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudElkIjoibW9iaWxlcmV0YWlsIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1tb2JpbGVyZXRhaWwiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC4xIn0.acg7pPIK89AVFQT0oYrMmBwBGe4hy6PYrQCYdNbSOQA49p6FZ5ZCdqBtxKrxy2DYxgBJZNhMJ-PPZ_WrmRwTAS1H-Udo0Dj9o8VQLDyG2PsrVv8jdsCrrlSnPIg978HF6eP2bh49G4JYZjuLqzuX2h29voFWEMvtCjKGPYTwrwG24uYCKyEr_nCUV8_7Ky6hTyxl10xxnQ5qGjo1Acbhs-F4omgi6H2I2H17PUPVkKdeMKAbsVbubzbMbijgYAUf3j4KbsxEArJ6KC6ZZDsFIIB7-xMBMZD3OYjpn6-3Mt1s_QAlp4I9bdkSS2dNFtP3U6OmTnHCGyYgmScD8FTffw",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxOGM5ZmRiNy1mNzQ0LTQ2ZjktODQ4Ni0wMTFjNWVkOWNkZDIifQ.eyJleHAiOjE2MDM3ODU4NTMsImlhdCI6MTYwMzc4NDA1MywianRpIjoiMGJmOWMyZjItNjM5YS00OGVkLTgzNzUtM2M0YTE0ODc2ZGU0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21pY3Jvc2VydmljZSIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9taWNyb3NlcnZpY2UiLCJzdWIiOiIxZGExM2RjMy0yNDQ1LTRlZTQtYjFhNS0zNjc2YzYyMjY4OTciLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoibW9iaWxlcmV0YWlsIiwic2Vzc2lvbl9zdGF0ZSI6IjJiMTM0ZTdjLTQ2YjEtNDBjZi1iMjJmLTgwNzA3MmFhYzBlNCIsInNjb3BlIjoicGhvbmUgcHJvZmlsZSBlbWFpbCJ9.dG67rD5TPQLSQY69Fhdh9am_t_SZoiL9MEuufD6eOvU",
"token_type": "bearer",
"not-before-policy": 0,
"session_state": "2b134e7c-46b1-40cf-b22f-807072aac0e4",
"scope": "phone profile email"
}

Why does springboot refresh the bean only once when I remove bean and add another bean?

I have a requirement to remove the corresponding Bean and then add another Bean, but I find that springboot only refreshes the injected Bean once
the springboot version is 2.1.5.RELEASE,JDK1.8
public class Users {
String name;
int age;
String gender;
public Users() {
}
public Users(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
#Component
public class UsersController implements ApplicationRunner {
#Autowired(required = false)
// #Resource
List<Users> users= Collections.emptyList();
#Override
public void run(ApplicationArguments args) {
System.out.println(users);
}
}
#RestController
public class HelloController implements
BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
ApplicationContext applicationContext;
DefaultListableBeanFactory defaultListableBeanFactory;
Users users;
int i=0;
#RequestMapping("/hello1")
public String index1() {
return "hello";
}
#RequestMapping("/hello3")
public Users index3() {
UsersController controller =
applicationContext.getBean(UsersController.class);
controller.run(null);
return null;
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry
registry) throws BeansException {
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
defaultListableBeanFactory= (DefaultListableBeanFactory)
beanFactory;
Users users=new Users("hello"+(i++),23,"fe");
this.users=users;
defaultListableBeanFactory.registerBeanDefinition(users.getName(),
BeanDefinitionBuilder.genericBeanDefinition(Users.class, () ->
users).getRawBeanDefinition());
}
#EventListener
public void onApplicationEvent(#NonNull EnvironmentChangeEvent
environmentChangeEvent) {
System.out.println("before
remove:"+applicationContext.getBean(Users.class));
defaultListableBeanFactory.removeBeanDefinition(users.getName());
Users users=new Users("hello"+(i++),23,"fe");
this.users=users;
defaultListableBeanFactory.registerBeanDefinition(users.getName(),
BeanDefinitionBuilder.genericBeanDefinition(Users.class,
() -> users).getRawBeanDefinition());
System.out.println("after:"+applicationContext.getBean(Users.class));
}
#Override
public void setApplicationContext(ApplicationContext
applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
only bootstrap.yml:
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
when start the application it will output [Users(name=hello0, age=23, gender=fe)],
and then visit the url by POST,
http://localhost:8080/actuator/refresh
it will output
before remove:Users(name=hello0, age=23, gender=fe)
after:Users(name=hello1, age=23, gender=fe)
then visit the url
http://localhost:8080/hello3
output
[Users(name=hello1, age=23, gender=fe)]
This is to be expected,if visit the url again
http://localhost:8080/actuator/refresh
output
before remove:Users(name=hello1, age=23, gender=fe)
after:Users(name=hello2, age=23, gender=fe)
This is also to be expected,but visit the url
http://localhost:8080/hello3
we hope it output
Users(name=hello2, age=23, gender=fe)
but it output
Users(name=hello1, age=23, gender=fe)
The output message is not what we expected.
If change #Autowired to #Resource in class UsersController, it should be normal.But it maybe null in real environment,the application will run fail if use #Resource
How can I solve this problem?
Thanks for your time.
#Resource
#Lazy
can star application properly.But it may cause error like
ERROR [main] com.Users - No qualifying bean of type 'java.util.List<com.Users>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=), #org.springframework.context.annotation.Lazy(value=true)}
Please take a look at this part of the documentation in order to refresh your beans at runtime: https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#refresh-scope

doFilter() overridden method httpRequest.getUserPrincipal() always return NullPoint Exception

I created spring boot application. i have used following dependencies.
Spring boot version is 2.1.0.RELEASE
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
<version>4.5.0.Final</version>
</dependency>
Security configuration class look like following.
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/actuator").authenticated();
http.headers().cacheControl().disable();
http.csrf().disable();
http.logout().logoutSuccessUrl("/assets/logout.html");
}
}
Filter class look like following:
#WebFilter(urlPatterns = "/*", initParams = {
#WebInitParam(
name = "keycloak.config.file",
value = "./config/saml.xml"
)})
public class SingleSignOnFilter extends SamlFilter {
private static final String loginGc = "/gc/";
private static final String logoutUrl = "/gc/assets/logout.html";
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestPath = httpRequest.getRequestURI();
boolean needAuthentication = !requestPath.equals(logoutUrl);
boolean alreadyLoginGc = !requestPath.equals(loginGc);
if (needAuthentication) {
if (alreadyLoginGc) {
super.doFilter(request, response, chain);
} else {
httpResponse.setHeader("Cache-Control", "no-cache, no
store");
super.doFilter(request, response, chain);
}
} else {
chain.doFilter(request, response);
}
}
}
when i call httpRequest.getUserPrincipal(); this method its will return Nullpoint Exception.
and when i trying to get authenticated user its will return annonymousUser.
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
auth.getName();
Saml.xml look like following:
<keycloak-saml-adapter>
<SP entityID="https://localhost/gc" sslPolicy="NONE">
<PrincipalNameMapping policy="FROM_NAME_ID"/>
<IDP entityID="************************">
<SingleSignOnService requestBinding="POST"
validateResponseSignature="true"
bindingUrl="********************************"/>
<SingleLogoutService requestBinding="REDIRECT"
responseBinding="REDIRECT"
redirectBindingUrl="***************************"/>
<Keys>
<Key signing="true">
<CertificatePem>
-----BEGIN CERTIFICATE-----
##########################
###### Dummy Data ########
##########################
-----END CERTIFICATE-----
</CertificatePem>
</Key>
</Keys>
</IDP>
</SP>
</keycloak-saml-adapter>
I'm not sure the filter implementation you are using handles the required logic for spring security underneath but it is clear that SecurityContextHolder is not populated since there are no adapters configured to do so.
Keycloak provides a Spring Security Adapter, try to use it. It will populate httpRequest.getUserPrincipal() as well.

Resources