restTemplate throw UnknownHostException when use service-name
I have add bean restTemplate
#Configuration
public class SpringCloudConfig {
#LoadBalanced
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
I use the Spring-cloud Greenwich.SR3 in parent pom
dependencies:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
yml :
#OAuth
security:
oauth2:
resource:
loadBalanced: true
token-info-uri: http://FLY-AUTH/oauth/check_token
client:
client-id: sanke
client-secret: sanke
scope: all
OAuth info in yml
Modify ResourceServerConfig file
#Configuration
#EnableResourceServer
#AllArgsConstructor
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
private final MyAccessDeniedHandler myAccessDeniedHandler;
private final RemoteTokenServices remoteTokenServices;
private final RestTemplate restTemplate;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().permitAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(entryPointUnauthorizedHandler)
.accessDeniedHandler(myAccessDeniedHandler);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
UserAuthenticationConverter userTokenConverter = new FlyUserAuthenticationConverter();
accessTokenConverter.setUserTokenConverter(userTokenConverter);
//Config restTemplete
remoteTokenServices.setRestTemplate(restTemplate);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
resources.tokenServices(remoteTokenServices);
resources.authenticationEntryPoint(entryPointUnauthorizedHandler);
}
}
Related
I need help finding a way to make Spring Security's form login endpoint work in swagger-ui with springdoc-openapi. I'm using SpringBoot 2.7.5 and Spring Security 5.7.4, and these are the rest of the project's dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>1.6.12</version>
</dependency>
</dependencies>
Spring Security simple configuration for securing /foos/ endpoints with form login and defining user login details.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/foos/**")
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder.encode("password"))
.roles("USER");
}
}
And the foos Controller:
#RestController
#RequestMapping("foos")
public class FooController {
#GetMapping(value = "/{id}")
public Foo findById(#PathVariable("id") final Long id) {
return new Foo(randomAlphabetic(6));
}
#GetMapping
public List<Foo> findAll() {
return Lists.newArrayList(new Foo(randomAlphabetic(6)));
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Foo create(#RequestBody final Foo foo) {
return foo;
}
}
Also supplying this property: springdoc.show-login-endpoint=true and the login endpoint is exposed in swagger-ui but the only problem is that the only request body type is application/json and this sends the credentials as json in the request body which results in null username/password in UsernamePasswordAuthenticationFilter.
Sample code repo: https://github.com/adrianbob/springdoc-form-login
Can the request body type be configured to application/x-www-form-urlencoded, so that form login works?
I've raised an issue which was categorised as a bug and fixed. The latest release does not have this problem anymore, version 1.6.13.
when iam adding the Spring Boot Starter Security dependency on my Projekt my STOMP Endpoint responding an 404 Code to my React frontend. I build a simple Demo Projekt with only web socket dependency. In this case everything works fine. When iam adding the security dependency without any configuration i get a 403. At this point everything is fine. When iam adding the same WebSecurityConfigurerAdapter implementation as the Main Projekt everything works fine aswell. But on my main Projekt it did not work. Everytime i get a 404 on my endpoint ws://localhost:8080/socket
I tried to get this work for one Week now... I cant figure it out where i should configure the Security part for the Sockets
The goul of all this is to stream progress information of some Tasks to the frontend. If you have any other solutions to build that i would be happy. It could be that websockets are not the best way to do that.
and btw. its my first Question on Stackoverflow please dont judge me if the formatting is not the best way :-)
Iam storing the User Informations in a h2 Database.
Here my Configurations and Dependencys for the Backend
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
The WebSecurityConfigurerAdapter implementation
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AppUserDetailService appUserDetailService;
private final Filter jwtAuthFilter;
#Autowired
public SecurityConfig(AppUserDetailService appUserDetailService, Filter jwtAuthFilter){
this.appUserDetailService = appUserDetailService;
this.jwtAuthFilter = jwtAuthFilter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(appUserDetailService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
.antMatchers("/auth/**","/oauth/**", "/topic/**", "/socket/**", "/app/**").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/**").permitAll().and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
}
JWTAuthFilter
#Slf4j
#Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JWTUtilService jwtUtil;
public JwtAuthFilter(JWTUtilService jwtUtil) {
this.jwtUtil = jwtUtil;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = getAuthToken(request);
try{
if(token != null && !token.isBlank()){
String username = jwtUtil.extractUsername(token);
setSecurityContext(username);
}
}catch (Exception e){
log.error("No valid Token found!", e);
}
filterChain.doFilter(request, response);
}
private void setSecurityContext(String username) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, "", List.of());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
private String getAuthToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if(authHeader != null){
return authHeader.replace("Bearer", "").trim();
}
return null;
}
}
AppUserDetailsService implementation
#Service
public class AppUserDetailService implements UserDetailsService {
private final AppUserRepo appUserRepo;
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
private final JWTUtilService jwtUtilService;
public AppUserDetailService(AppUserRepo appUserRepo, JWTUtilService jwtUtilService) {
this.appUserRepo = appUserRepo;
this.jwtUtilService = jwtUtilService;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return appUserRepo.findByUsername(username)
.map(appUser -> User
.withUsername(username)
.password(appUser.getPassword())
.authorities("user")
.build())
.orElseThrow(()-> new UsernameNotFoundException("Username does not exist: "+username));
}
public String registerUser(AppUserDTO user) {
if(!userExisting(user)){
user.setPassword(encoder.encode(user.getPassword()));
appUserRepo.save(user);
return jwtUtilService.createToken(new HashMap<>(), user.getUsername());
}else{
throw new UserExistsException("User is currently existing.");
}
}
public boolean userExisting(AppUserDTO user){
return appUserRepo.findByUsername(user.getUsername()).isPresent();
}
}
WebSocketMessageBrokerConfigurer implementation
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket").setAllowedOriginPatterns("*");
}
}
Here my simplified version of the implementation of the React frontend
Top Level Component
import {StompSessionProvider} from "react-stomp-hooks";
<StompSessionProvider url={"ws://localhost:8080/socket"} topics={['/topic/progress']} onConnect={()=>{console.log("Connected")}} onDisconnect={()=>{console.log("Disconnected")}} onError={(err)=>{console.log(err)}}>
<Home/>
</StompSessionProvider>
Home Component
import {useSubscription} from "react-stomp-hooks";
export default function ZapContinousHome() {
useSubscription("/topic/progress", (message) => setMessage(message.body));
return(
<h1>Home</h1>
)
}
I have a spring boot admin server. Here are the files for the server:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.17.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-boot-admin.version>1.5.7</spring-boot-admin.version>
</properties>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-login</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-dependencies</artifactId>
<version>${spring-boot-admin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.properties
spring.application.name=spring-boot-admin-server
security.user.name=admin
security.user.password=admin
server.port = 9090
WebSecurityConfig.java
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.permitAll();
http
.logout().logoutUrl("/logout");
http
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/login.html", "/**/*.css", "/img/**", "/third-party/**")
.permitAll();
http
.authorizeRequests()
.antMatchers("/**")
.authenticated();
http.httpBasic();
}
}
The server is up and running and accessible at localhost:9090. But when I start my client application, it registers to the server but none of the endpoints are accessible because of security I presume.
Here's what the admin page looks like:
For some reason the application always shows as DOWN. I get this error in the server console:
2018-11-21 15:02:21.475 INFO 1893 --- [ updateTask1] d.c.boot.admin.registry.StatusUpdater : Couldn't retrieve info for Application [id=d83f0885, name=PETE, managementUrl=http://localhost:8081/demo/, healthUrl=http://localhost:8081/demo/health/, serviceUrl=http://localhost:8081/demo/]: 403 - {timestamp=1542834141473, status=403, error=Forbidden, message=Access Denied, path=/demo/login}
My demo application is a web app with a login page. It has the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.5.7</version>
</dependency>
It has spring boot security, here's the security config class:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private AuthenticationFailureHandler failureHandler;
#Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
#Autowired
private Environment env;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(),
UsernamePasswordAuthenticationFilter.class)
.logout().invalidateHttpSession(true).deleteCookies("token", "JSESSIONID")
.logoutSuccessUrl(Routes.LOGOUT.getValue()).and().exceptionHandling().accessDeniedPage("/403");
// Disable HSTS so that "proceed anyways" works for SSL certs
http.headers().httpStrictTransportSecurity().disable();
}
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List<String> pathsToSkip = Arrays.asList("/login", "/loginPost", "/login-failure", "/error",
"/**/*.js", "/**/*.png", "/**/*.css", "/**/*.woff*", "/**/*.ttf");
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, "/**");
JwtTokenAuthenticationProcessingFilter filter = new JwtTokenAuthenticationProcessingFilter(failureHandler,
matcher, env);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider);
}
#Bean(name = "loginAuthenticationRestTemplateBean")
RestTemplate restTemplate() {
return new RestTemplate();
}
}
What do I need to do client side to open up the actuator endpoints so the admin page can access them and show proper status.
Edit 1: There's another error in the admin server console:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/html;charset=UTF-8]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:110) ~[spring-web-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:932) ~[spring-web-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:916) ~[spring-web-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:663) ~[spring-web-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:636) ~[spring-web-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:557) ~[spring-web-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at de.codecentric.boot.admin.web.client.ApplicationOperations.doGet(ApplicationOperations.java:68) ~[spring-boot-admin-server-1.5.7.jar:na]
at de.codecentric.boot.admin.web.client.ApplicationOperations.getHealth(ApplicationOperations.java:58) ~[spring-boot-admin-server-1.5.7.jar:na]
at de.codecentric.boot.admin.registry.StatusUpdater.queryStatus(StatusUpdater.java:111) [spring-boot-admin-server-1.5.7.jar:na]
at de.codecentric.boot.admin.registry.StatusUpdater.updateStatus(StatusUpdater.java:65) [spring-boot-admin-server-1.5.7.jar:na]
at de.codecentric.boot.admin.registry.StatusUpdateApplicationListener$1.run(StatusUpdateApplicationListener.java:47) [spring-boot-admin-server-1.5.7.jar:na]
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_121]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_121]
at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_121]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_121]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_121]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
I tried adding adding this message converter:
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
/**
* {#inheritDoc}
* <p>This implementation is empty.
*
* #param converters
*/
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> newConverters = converters.stream().filter(c -> !(c instanceof MappingJackson2HttpMessageConverter)).collect(Collectors.toList());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// Note: here we are making this converter to process any kind of response,
// not only application/*json, which is the default behaviour
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
newConverters.add(converter);
converters.clear();
converters.addAll(newConverters);
}
}
Still same error.
As per the this documentation just setting the below property should expose the actuator end points without security.
management.security.enabled=false
If still does not works out then you may need to modify your client side configure(HttpSecurity) method something like below
'''
.authorizeRequests()
.antMatchers("/actuator-end-points").permitAll()
.and()
.addFilterBefore(jwtTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
'''
I am trying to disable spring security for request to frontend. I am using oauth2-auto-configuration as a dependency, also i have removed spring starter security from maven. Also i need to say that everything is working from Postman.
Login required when requesting API's from frontend
Security Config
#Configuration
#EnableWebSecurity(debug = true)
#Order(SecurityProperties.BASIC_AUTH_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public static PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Autowired
public UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/","/**").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.and().httpBasic().disable();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
application.properties
spring.data.mongodb.database=music-test-oauth2-2
security.oauth2.resource.id=oauth2_application
access_token.validity_period=3600
refresh_token.validity_period=10000
dependencies from pom.xml
<dependencies>
<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.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.5</version>
</dependency>
</dependencies>
CORS Filter
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
Thanks.
Here is a simple CORF-Filter that you can try:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
response.setHeader("Access-Control-Max-Age", "3600");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig config) throws ServletException {
}
}
Maybe your request Headers are not correct, try to use this code:
public obtainAccessToken(username: string, password: string): Promise<void> {
let params = new URLSearchParams();
params.append('username', username);
params.append('password', password);
params.append('grant_type','password');
params.append('client_id','fooClientIdPassword');
let headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Basic '+btoa("fooClientIdPassword:secret")});
let options = new RequestOptions({ headers: headers });
return this.http.post(environment.apiEndpointAuth + 'user/login', params.toString(), options)
.toPromise().then(response => {
this.saveToken(response.json());
})
.catch(this.handleError);
}
Implement Following CORS class and see if it's working-
#Component
public class CorsFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (exchange != null) {
exchange.getResponse().getHeaders().add("Access-Control-Allow-Origin", "*");
exchange.getResponse().getHeaders().add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
exchange.getResponse().getHeaders().add("Access-Control-Allow-Headers",
"DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range");
exchange.getResponse().getHeaders().add("Access-Control-Max-Age", "1728000");
if (exchange.getRequest().getMethod() == HttpMethod.OPTIONS) {
exchange.getResponse().getHeaders().add("Access-Control-Max-Age", "1728000");
exchange.getResponse().setStatusCode(HttpStatus.NO_CONTENT);
return Mono.empty();
} else {
exchange.getResponse().getHeaders().add("Access-Control-Expose-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range");
return chain.filter(exchange);
}
} else {
return chain.filter(exchange);
}
}
}
Second you need to scan CorsFilter class for that in you main class do following-
#ComponentScan(value = "com.cors.filter", // Your CorsFilter class package path
useDefaultFilters = false)
public class MainClass {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.
run(MainClass.class, args);
}
}
Update:
Add Following dependency in pom.xml
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.4.RELEASE</version>
</dependency>
The problem was that i should have to pass the content type on headers as 'application/x-www-form-urlencoded' also i have to pass the Basic Auth to header generated from client_id also client_secert.
Thanks :)
I am building rest API using Spring Boot v1.3.3. API is secured by Spring Security. I have implemented custom user details service to have custom principal in authentication context.
I needed to share sessions of API with other Spring app so I choosen to implement Spring Session with Redis server in my app using this tutorial docs.spring.io/spring-session/docs/current/reference/html5/guides/security.html. Unfortunetly it caused Authentication Principal to stop working. When I am trying to get current Principal either by annotation #AuthenticationPrincipal CustomUserDetails user or by SecurityContextHolder.getContext().getAuthentication().getPrincipal() it returns my custom user details but with Id = 0 and all fields set to null
(screen from debugging). I can't even get username from SecurityContextHolder.getContext().getAuthentication().getName().
After I commented Redis code and maven dependency it works (see debug screen). How to make it working with Spring Session and Redis server?
Here is some code from the app:
Some example method to check Principal
#RequestMapping(value = "/status", method = RequestMethod.GET)
public StatusData status(#AuthenticationPrincipal CustomUserDetails user) {
User user2 = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user != null) {
String name = user.getUsername();
return new StatusData(name);
} else return new StatusData(null);
}
Application and Redis config:
#Configuration
#EnableRedisHttpSession
public class AppConfig {
#Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
#Bean
public ShaPasswordEncoder shaEncoder() {
return new ShaPasswordEncoder(256);
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean(name = "messageSource")
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setBasename("messages/messages");
return resourceBundleMessageSource;
}
#Bean
public Validator basicValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
public AppConfig() {
DateTimeZone.setDefault(DateTimeZone.UTC);
}
}
Initializer (used for Redis Session)
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
SecurityInitializer (used for Redis session)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(WebSecurityConfig.class, AppConfig.class);
}
}
WebSecurityConfig (Spring Security config)
#Configuration
#EnableWebSecurity
//#EnableWebMvcSecurity
#ComponentScan(basePackageClasses = {UserRepository.class, CustomUserDetailsService.class})
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private HttpAuthenticationEntryPoint httpAuthenticationEntryPoint;
#Autowired
private AuthSuccessHandler authSuccessHandler;
#Autowired
private AuthFailureHandler authFailureHandler;
#Autowired
private HttpLogoutSuccessHandler logoutSuccessHandler;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* Persistent token repository stored in database. Used for remember me feature.
*/
#Bean
public PersistentTokenRepository tokenRepository() {
JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
db.setDataSource(dataSource);
return db;
}
/**
* Enable always remember feature.
*/
#Bean
public AbstractRememberMeServices rememberMeServices() {
CustomTokenPersistentRememberMeServices rememberMeServices = new CustomTokenPersistentRememberMeServices("xxx", customUserDetailsService, tokenRepository());
rememberMeServices.setAlwaysRemember(true);
rememberMeServices.setTokenValiditySeconds(1209600);
return rememberMeServices;
}
/**
* Configure spring security to use in REST API.
* Set handlers to immediately return HTTP status codes.
* Enable remember me tokens.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(httpAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/cookie", "/register", "/redirect/**", "/track/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(authSuccessHandler)
.failureHandler(authFailureHandler)
.and()
.logout()
.permitAll().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)
.and()
.rememberMe().rememberMeServices(rememberMeServices())
.and()
.headers()
.addHeaderWriter(new HeaderWriter() {
/**
* Header to allow access from javascript AJAX in chrome extension.
*/
#Override
public void writeHeaders(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
String corsUrl = "https://mail.google.com";
if (httpServletRequest.getHeader("Origin") != null && httpServletRequest.getHeader("Origin").equals(corsUrl)) {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "https://mail.google.com");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Location");
}
}
});
}
/**
* Set custom user details service to allow for store custom user details and set password encoder to BCrypt.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
}
Maven dependencies
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>models</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.3.Final</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.jadira.usertype</groupId>
<artifactId>usertype.core</artifactId>
<version>3.1.0.CR1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.ganyo</groupId>
<artifactId>gcm-server</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
I solved this problem. It turned out that Spring-Session serializes the Principal object. My custom implementation of UserDetails was subclass of Hibernate Model User class. I solved it by implementing Serializable interface in my custom UserDetails, User model and all classes used in this model.
To make it work in my case I had as well to make sure the Servlet filters were set up in the right order.
For me that was:
...
<filter-name>CharacterEncodingFilter</filter-name>
...
<filter-name>springSessionRepositoryFilter</filter-name>
...
<filter-name>springSecurityFilterChain</filter-name>
...
<filter-name>csrfFilter</filter-name>
...
After that, the principal was not empty anymore.
As #yglodt said, the problem is the filter's order in the spring security filter chain.
In Java Config way, just set an higher precedence to Redis configuration class
#Configuration
#EnableRedisHttpSession
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RedisConfig extends AbstractHttpSessionApplicationInitializer {
#Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
}
I set the highest precedence, but maybe something lower is enough.
Now the principal should be correctly populated.
The order of the HttpSecurity chain is important:
Does not work, and leaves principal name null:
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.anyRequest().authenticated()
Works correct:
.authorizeRequests()
.anyRequest().authenticated()
.antMatchers("/api/register").permitAll()
EDIT: 2022 This answer is outdated and will throw an IllegalStateException according to #BendaThierry.com