I am currently developing an application with SpringBoot 2.0.0.M7 and spring-boot-starter-webflux. It also use Spring Boot WebFlux Functional Rest API and Spring Boot Actuator.
Application itself is very simple and it works as expected. I can POST book (/api/v1/book) to the ArrayList and GET all books (/api/v1/book) from ArrayList. Rest API Documentation is available on (/) URL and Actuator endpoint is available on (/actuator) URL.
Problem is that how can I secure my WebFlux Functional Rest API with OAuth2 and JWT? I can find many examples OAuth2 with JWT but they are for older Spring Boot. Here is one example https://dzone.com/articles/spring-oauth2-with-jwt-sample SpringBoot 2.0.0.M7 has a support on OAuth2 Login but this example https://spring.io/blog/2017/09/15/spring-security-5-0-0-m4-released does not tell how to use OAuth2 with JWT in a Rest API. I did not even find a full source code of that example. This video https://www.youtube.com/watch?v=LzNhedHo7pM and this example https://medium.com/#mohitsinha.it/spring-boot-reactive-tutorial-ffab309b2dcd will only use a basic authentication.
Does anyone know how I can secure my (/api/v1/book) endpoint using #EnableWebFluxSecurity annotation and OAuth2 with JWT?
Below has example on my RoutingConfiguration class.
#Configuration
#EnableWebFlux
public class RoutingConfiguration implements WebFluxConfigurer {
private final static String API_URL = "api";
private final MeterRegistry registry;
public RoutingConfiguration(MeterRegistry registry) {
this.registry = registry;
}
#Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping(API_URL + "/**").allowedMethods("GET", "POST");
}
#Bean
public RouterFunction<?> indexRouter(final IndexHandler indexHandler, final ErrorHandler errorHandler) {
RouterFunctionMetrics metrics = new RouterFunctionMetrics(this.registry);
return route(GET("/"), indexHandler::getRestApiDocs).filter(metrics.timer("http.request.index.page"));
}
#Bean
public RouterFunction<?> bookRouter(final BookHandler handler, final ErrorHandler errorHandler) {
return
nest(path(API_URL + "/v1/book"),
nest(accept(MediaType.APPLICATION_JSON),
route(GET("/"), handler::getAllBooks)
.andRoute(POST("/"), handler::saveBook)
).andOther(route(RequestPredicates.all(), errorHandler::notFound))
);
}
}
Related
I am following this answer: Best practice for REST token-based authentication with JAX-RS and Jersey to implement the REST API authentication. But the JAX-RS Authentication Filter is not triggered via #Secured annotation.
I finished until the part Securing your REST endpoints by adding #Secured on the method.
This is how I am doing this.
Secured.java
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured { }
AuthenticationFilter.java
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String AUTHENTICATION_SCHEME = "Bearer";
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the Authorization header from the request
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}
// Extract the token from the Authorization header
String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim();
try {
validateToken(token);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// Authentication scheme comparison must be case-insensitive
return (authorizationHeader != null &&
authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " "));
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
// Abort the filter chain with a 401 status code
// The "WWW-Authenticate" is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME)
.build());
}
private void validateToken(String token) throws Exception {
// Check if it was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
MyApis.java
#Path("api")
public class MyApis {
#GET
#Secured
#Path("me")
#Produces(MediaType.APPLICATION_JSON)
public Map<String, X500Name> whoAmI() {
return ImmutableMap.of("me", legalName);
}
// other APIs
}
When I call /api/me, I can get the response directly without any authentication header provided. It seems like the filter is not triggered or not registered properly.
I have seen this question JAX RS, my filter is not working, but it does not solve my problem.
And how I understand the following is that web.xml is not needed right?
This solution uses only the JAX-RS 2.0 API, avoiding any vendor specific solution. So, it should work with the most popular JAX-RS 2.0 implementations, such as Jersey, RESTEasy and Apache CXF.
It's important mention that if you are using a token-based authentication, you are not relying on the standard Java EE web application security mechanisms offered by the servlet container and configurable via application's web.xml descriptor.
Late for answer, but the solution to this problem is to add AuthenticationFilter to your classes.
public class App extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(MyApis.class);
classes.add(AuthenticationFilter.class);
return classes;
}
}
I have a Windows service "A" being used for authentication purposes (NOT managed by us) and I have Spring-boot based REST Api service "B" (managed by us) which uses Zuul to route traffic. There is an external service "C" (NOT managed by us) that needs to talk to the Windows service through our REST Apis. Since "A" uses NTLM authentication we need to pass the request body from "C" and add the ntlm credentials in the headers at "B" and route the traffic using zuul.
My question is, how do I add NTLM credentials in Java to the routed traffic in zuul headers?
~ Jatin
You need to write your own ZuulFilter.
Something along the lines of
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.ZuulFilter;
public class MyFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// now add your headers to the request
return null;
}
}
In your app just make sure the filter bean is created and it will be automatically registered:
#EnableZuulProxy
#SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
}
Have a look at this guide for more info.
Zuul will work fine with Spring Session. There are many blogs about this.
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot.html
I have a REST service that uses OAuth2 authentication and that provides an endpoint to request a token with the client_credentials grant type. The application is based on Spring Boot.
So far I figured out I can request a token with something like:
#SpringBootApplication
#EnableOAuth2Client
public class App extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
//...
#Override
protected void configure(HttpSecurity http) throws Exception {
// Does nothing - to allow unrestricted access
}
#Bean
protected OAuth2RestTemplate myTemplate() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setAccessTokenUri("http://localhost:8080/oauth/token");
details.setClientId("theClient");
details.setClientSecret("thePassword");
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
}
#RestController
public class TestController {
#Autowired
OAuth2RestTemplate myTemplate;
#RequestMapping("/token")
private String getToken() {
return myTemplate.getAccessToken().getValue();
}
}
And it almost works, but whenever I call the /token endpoint, there's an exception:
org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:88) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
...
The exception is thrown here, but I'm not sure how I can make Spring use context authentication other than AnonymousAuthenticationToken. In fact, I don't want any authentication from the client, because anonymous is perfectly okay. How can I achieve this?
I use spring security login. Now I'm trying to add spring social facebook login, but I get many error information.
First, when I try to use the same method like spring social guide, I can't #Autowired private Facebook facebook
I found a solution
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository
.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
Next, I get the error "cannot find bean". I have to add:
#Bean
public ConnectionRepository connectionRepository() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null) {
throw new IllegalStateException(
"Unable to get a ConnectionRepository: no user signed in");
}
return usersConnectionRepository().createConnectionRepository(
authentication.getName());
}
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(facebookid,
facebookSecure));
return registry;
}
#Bean
public AuthenticationNameUserIdSource authenticationNameUserIdSource(){
return new AuthenticationNameUserIdSource();
}
#Bean
public ConnectController connectController(
ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator,
connectionRepository);
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
return new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator(), Encryptors.noOpText());
}
After that, I have other issue java.lang.NoSuchMethodError: org.springframework.social.security.SocialAuthenticationFilter.getFilterProcessesUrl()Ljava/lang/String;
#Bean
public SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() {
SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(facebookid,
facebookSecure));
return registry;
}
#Bean
public SocialAuthenticationFilter socialAuthenticationFilter()
throws Exception {
SocialAuthenticationFilter filter = new SocialAuthenticationFilter(
authenticationManager(), authenticationNameUserIdSource(),
usersConnectionRepository(), socialAuthenticationServiceLocator());
filter.setFilterProcessesUrl("/login");
filter.setSignupUrl("/signup");
filter.setConnectionAddedRedirectUrl("/home");
filter.setPostLoginUrl("/home"); // always open account profile
// page after login
// filter.setRememberMeServices(rememberMeServices());
return filter;
}
but always is the same.
This is my http configuration
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/home", "/css/**", "/**/*.css*", "/", "/signup",
"/facebook", "/signup.xhtml").permitAll().anyRequest()
.authenticated().and().formLogin().loginPage("/login").loginProcessingUrl("/login/authenticate")
.defaultSuccessUrl("/home").failureUrl("/login")
.permitAll().and().logout().logoutUrl("/logout")
.invalidateHttpSession(true).logoutSuccessUrl("/").and()
.apply(new SpringSocialConfigurer());
And controller
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage() {
return "redirect:/login/authenticate/connect/facebook";
}
I did a whole tutorial. Next, I removed SocialConfigurer implementation and created the same (not #Override, only #Bean) social documentation.
'Normal login '(spring security) works fine, but I can't configure spring social with spring security. I use JSF and .XHTML files.
Maybe someone knows where I make the mistakes?
Thanks for your help.
It looks like Spring Security removed getFilterProcessesUrl() in Spring Security 4.0.0.RC1 (it was marked as deprecated anyways).
It seems that other project filters have not been updated?
Try rolling back to 4.0.0.M2 or use the 3.2 train.
Please notice that spring security 4 will not accept spring social 1.1.0. Please upgrade all spring social dependencies(config, core, security and web) to 1.1.2.RELEASE. You can leave your spring social Facebook to 1.1.0
As hinted in my comment, you have the wrong version of some library. My intelligent guess is that version of Spring Security is wrong. From what I can find, you should use a version in the 3.2.x series (for example 3.2.5) of Spring Security.
Consider using version 1.1.4.
this is solved in spring-social-security 1.1.4.RELEASE (or perhaps some version before).
https://github.com/spring-projects/spring-social
I am using Spring Security with OAuth2. It's working fine except login success and failure handlers.
Like in spring web security OAuth2 does not have clearly defined success and failure handlers hooks to update DB and set response accordingly.
What filter do I need to extend and what should its position be in the Spring Security filter chain?
Specify successHandler and failureHandler for oauth2login method:
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${successUrl}")
private String successUrl;
#Value("${failureUrl}")
private String failureUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.successHandler(successHandler())
.failureHandler(failureHandler());
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
return new SimpleUrlAuthenticationSuccessHandler(successUrl);
}
#Bean
SimpleUrlAuthenticationFailureHandler failureHandler() {
return new SimpleUrlAuthenticationFailureHandler(failureUrl);
}
}
Tested for Spring Security 5.0.6
I personally use
#Component
public class MyAuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
System.out.println("Authenticated");
}
}
Additional informations in response can be set by CustomTokenEnhancer
This is a nice tutorial about how to use spring boot with oauth2. Down to the road they show how to configure sso filter by hand:
private Filter ssoFilter(OAuth2Configuration client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId()));
//THIS IS THE PLACE YOU CAN SET THE HANDLER
filter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler());
return filter;
}
They didn't provide the line you need, here it is.
The success handler and failure handler are defined in the form-login (if you use Spring's XML). It is not different than any other spring-security definitions:
<security:form-login
login-page="/login/login.htm"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/login/login.htm?login_error=1" />
and you can find the handler here.
The "failure handler" is pretty similar.