#oauth2.hasScope in spring boot + keycloak - spring-boot

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"
}

Related

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...

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

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.

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.

How to create filter in Spring RESTful for Prevent XSS?

i use from Spring 4.2.6.RELEASE and backend is rest services.
and now I can not have a filter for Prevent XSS
my filter is:
#Component
#Order(1)
public class XSSFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
}
}
and XSSRequestWrapper is :
public class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
#Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = stripXSS(values[i]);
}
return encodedValues;
}
#Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return stripXSS(value);
}
#Override
public String getHeader(String name) {
String value = super.getHeader(name);
return stripXSS(value);
}
private String stripXSS(String value) {
return StringEscapeUtils.escapeHtml(value);
}
}
and in WebConfig extends WebMvcConfigurerAdapter Class:
// -----------------------------------------------------
// Prevent XSS
// -----------------------------------------------------
#Bean
public FilterRegistrationBean xssPreventFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new XSSFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
My Rest Class is:
#RestController
#RequestMapping("/personService")
public class PersonController extends BaseController<PersonDto, PersonCriteria> {
#RequestMapping( value= "/test" )
private void getTest2(#RequestParam String name) {
System.out.println(name);
System.out.println( StringEscapeUtils.escapeHtml(name) );
}
}
But it does not work, without any Error or Exception.
How can I do this and create my own filter? I use only Java Config and no XML.
in my Controller I was forced again use StringEscapeUtils.escapeHtml(name) and this is bad.
I’ve created a fully executable sample project based on your codes.
Everything goes fine, you can download full source from my github https://github.com/mehditahmasebi/spring/tree/master/spring-xss-filter and for running command “mvnw spring-boot:run” and in browser type: http://localhost:8080/personService/test?name=foobar, so you can see result in XSSRequestWrapper.stripXSS.
I hope this source code will help you.
Some explanation :
Project structure :
POM.xml
WebConfig.java for Spring web config
SpringBootApplication.java for starting up application
your classes (PersonController.java, XSSFilter.java and XSSRequestWrapper.java)
dive into them, but I was only copy important lines :
pom.xml
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
WebConfig.java (at the bottom lines you can see your bean) :
#Configuration
#EnableWebMvc
#EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedMethods("GET, POST, PATCH, PUT, DELETE, OPTIONS")
.allowedOrigins("*");
}
#Bean
public FilterRegistrationBean xssPreventFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new XSSFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
SpringBootApplication.java (for starting up project) :
#SpringBootApplication
public class SpringbootApplication extends SpringBootServletInitializer {
/**
* tomcat deployment needed
*/
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
System.out.println("Spring boot application started!");
}
}
The other java source files is exactly as you are , but with 2 changes :
first, I've added a sysout line to see trace of your code without debugging:
private String stripXSS(String value) {
if(value != null)
System.out.println("escapeHTML work successfully and escapeHTML value is : " + StringEscapeUtils.escapeHtml(value));
return StringEscapeUtils.escapeHtml(value);
}
and the second change is, I commented escapeHtml from PersonController as you said is not a good idea:
#RequestMapping( value= "/test" )
private void getTest2(#RequestParam String name) {
System.out.println(name);
// System.out.println( StringEscapeUtils.escapeHtml(name) );
}
You can find all the source at my github https://github.com/mehditahmasebi/spring/tree/master/spring-xss-filter

Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type:

I am trying to expose REST service, but while hitting it from POSTMAN i am getting below :
WARNING: Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.ArrayList
Where as i have also included below jar files which are required :
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.1</version>
</dependency>
Here is my REST controller Code :
#RestController
#RequestMapping("/MayankAPI")
public class TestRestAPI {
#RequestMapping(value="/sayHello" , method = RequestMethod.POST)
public TestPojo postData(#RequestBody String payload) {
System.out.println("Hello post"+payload);
TestPojo payload1=new TestPojo();
payload1.setStudentName("Jack");
return payload1;
}
#RequestMapping(value="/sayHello" , method = RequestMethod.GET)
public List<TestPojo> getData(String payload) {
System.out.println("Hello get"+payload);
List<TestPojo> payload1=new ArrayList<TestPojo>();
TestPojo tp = new TestPojo();
tp.setStudentName("Jack");
payload1.add(tp);
return payload1;
}
Here is my bean which i am trying to return :
public class TestPojo {
private String studentName;
private String studentId;
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String studentId) {
this.studentId = studentId;
}
}
Please help me where i am doing wrong.
I know it too late but still
Alternate Solution:
you need to enable Spring project as Web MVC as follow:
#Configuration
#EnableWebMvc
#ComponentScan("basePackages = com.test")
public class MiBenefitConfiguration
{
}
This is happening because MappingJackson2HttpMessageConverter is not registered in my App config file as below.
#Configuration
#ComponentScan("basePackages = com.test")
public class MiBenefitConfiguration extends WebMvcConfigurationSupport{
#Bean
public ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
#Bean
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(getObjectMapper());
return converter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(messageConverter());
addDefaultHttpMessageConverters(converters);
}
}

Resources