I am trying to set up Cross Site Forging protection for my site that uses Spring MVC. My idea was to send a token in the HTML request header and verify it using AOP like this:
#Aspect
#Component
public class RequestMappingInterceptor {
#Before("execution(#org.springframework.web.bind.annotation.RequestMapping * *(..)) && args(request,..)")
public void before(JoinPoint point, HttpServletRequest request) throws Throwable {
UserEntity loggedUser = ((AmsUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserEntity();
String encodedToken = Base64.encodeBase64String(SessionEncodingUtils.encryptDecryptString(loggedUser.getId() + ";" + request.getSession(true).getId().hashCode()).getBytes());
if (!encodedToken.equals(request.getHeader("csrfToken"))) {
throw new RuntimeException("go.away");
}
}
}
However this does not work and i am not sure why. Shouldnt this intercept any method adnotated with #RequestMapping which contain a request argument? Any help would be appreciated
Related
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
I'm using OpenApi for Spring Boot application and I have authorization logic with JWT. The authorization request at /api/v1/login is intercepted and JSON is returned with the user token:
{
"Bearer": "token for user"
}
Security implementation responsible for capturing logins:
#Component
#RequiredArgsConstructor
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
// handler returns body JSON with JWT
}
}
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
var authFilter = new JsonObjectAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler );
authFilter.setAuthenticationFailureHandler(RestAuthenticationFailureHandler );
authFilter.setAuthenticationManager(super.authenticationManager());
authFilter.setFilterProcessesUrl("/api/v1/login"); // <- custom login URL
return authFilter;
}
}
It works fine, I don't have to put a separate /api/v1/login endpoint in the controller, so it is not taken into account when creating OpenAPI documentation. However, I want to have this endpoint documented and accessible from there as follows:
My first idea was to just create an interface to add appropriate annotations (assume BearerToken and AuthCredentials are my tranfer objects in correct format):
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/v1/login")
#Tag(name = "login")
interface LoginController {
#PostMapping
BearerToken login(#RequestBody AuthCredentials authCredentials);
}
However Spring does not register the interface as a beana, an implementation has yet to be provided, so OpenAPI does not add an entry to the documentation. So I turned the interface into a normal class:
#RestController
#RequestMapping("/api/v1/login")
#Tag(name = "login")
class LoginController {
#PostMapping
public BearerToken login(#RequestBody AuthCredentials authCredentials){
return new BearerToken();
}
}
Documentation is generated correctly, but I have a problem with this method. The implementation of the login() method suggests a completely different behavior than what actually takes place underneath in onAuthenticationSuccess().
Therefore, I am looking for a different way to achieve the desired effect.
As per OpenAPI implementation, there is a class OpenAPIService which has a build() method that does the following:
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(RestController.class));
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(RequestMapping.class));
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(Controller.class));
The reason as to why what you want to achieve is not possible is that OpenAPI library relies on Spring annotations that I provided above and has no knowledge of custom ant matcher paths like the one you added:
JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
var authFilter = new JsonObjectAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler );
authFilter.setAuthenticationFailureHandler(RestAuthenticationFailureHandler );
authFilter.setAuthenticationManager(super.authenticationManager());
authFilter.setFilterProcessesUrl("/api/v1/login"); // <- custom login URL
return authFilter;
}
Usually in production-ready applications developers write their own /authenticate API that checks username + password pair against a datasource (mySQL/postgreSQL/other).
This /authenticate API would be whitelisted so that security is not required to attempt login (some people store number of attempts per IP in Redis in case they want to block people from bruteforcing the password).
All other APIs would have to go through public class JwtAuthenticationFilter extends AuthenticationFilter { which simply validates the token and allows request to continue (if token is valid).
If you need more hints let me know.
I have a spring boot app that uses spring security.I implemented form based auth and it works well. I want the app to serve as the backend of an angular app i've built.I know about CORS but how can i add JWT auth to the existing spring boot app,is it recommended.
I would recommend AOP concept to authenticate/validate your jwt token.
First thing you need to create a custom annotation. Named it as JWTsecured
#Component
#Target(value = {ElementType.METHOD, ElementType.TYPE})
#Retention(value = RetentionPolicy.RUNTIME)
public #interface JwtSecured {
}
Now You have a controller in which your api need to be JWT secured.
#RestController
public class YourController {
#GetMapping(value="/testApi")
#JWTsecured
public void isTestApi() {
}
}
Now you have to write an aspect to validate your token...
#Component
#Aspect
public class JWTsecuredAspect {
#Around(value =" #within(com.JWTSecured) || #annotation(com.JWTSecured)")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
String token = request.getHeader("Authorization");
if(!isTokenValidated(token)){
throw CustomException("Invalid Token.")
}
}
}
This is how you can use it along with auth.
There are several other ways.
Feel free to contact
This question already has an answer here:
How to handle Spring Boot's redirection to /error?
(1 answer)
Closed 7 years ago.
I'm developing a spring boot app and I'm following this reply,
it all works fine despite some adaptations required to make it work on spring boot but the problem is when I call:
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
it returns "404 Not Found" instead of "401 Unauthorized", the method is called, it catches the exception but it return the wrong status.
Obs: If I remove the constraint of the filter it works normally.
the filter:
#Secured
#Provider
#Component
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new NotAuthorizedException("Authorization header must be provided");
}
String token = authorizationHeader.substring("Bearer".length()).trim();
try {
validateToken(token);
} catch (Exception e) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private void validateToken(String token) throws Exception {
}
}
the resource :
#Component
#Path("/")
public class Hello {
#Secured
#GET
#Path("/hello")
public String test() {
return "Hello!";
}
#GET
#Path("/world")
public String world() {
return "World!";
}
}
I assume that you are using the Jersey API to build your RESTful interface. The 404 you are receiving relates to (I think) the #Path (Jersey) or #RequestMapping (spring).
If you post more code relating to the path of your API, then we can help further.
Thank you.
If you look to the top level path #Path("/"). You're adding another forward-slash so the uri will end up looking like:
http://localhost.com//hello
http://localhost.com//world
To fix this remove the forward-slash from the methods #Path annotation, and rebuild. This should result in API endpoints which look like
http://localhost.com/hello
http://localhost.com/world
Right now, I'm learning about implementing REST API with a Spring Security Framework.
My question is, after success login with spring security, how can i send the request to server and make sure the server know that i am have been authorized (already login with success)?
I have a some experiment code to do testing
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { WebAppConfig.class, SecurityConfig.class })
public class TheTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy filterChainProxy;
protected MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders//
.webAppContextSetup(wac)//
.addFilter(filterChainProxy)//
.build()//
;
}
#Test
public void testDoingArequest() throws Exception {
// login here
HttpSession session = mockMvc.perform(//
//
post("/login-process")//
.param("username", "theusername")//
.param("password", "thepassword")//
)//
.andDo(print())//
.andExpect(status().isFound())//
.andReturn().getRequest().getSession()//
;
// login is success and now trying to call request
this.mockMvc.perform(//
get("/doingRequest")//
.session((MockHttpSession) session)// <-- where this part must added to?
)//
.andExpect(status().isOk())//
.andDo(print())//
;
}
}
-
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//
.antMatchers("/doingRequest").authenticated()//
.anyRequest().permitAll()//
.and()//
.csrf().disable()//
.formLogin()//
.loginPage("/")//
.loginProcessingUrl("/login-process")//
.defaultSuccessUrl("/");
}
-
#Controller
public class TheController {
#RequestMapping(value = "doingRequest", method = RequestMethod.GET)
#ResponseBody
public String doingSomething() {
return "Only authorized user can read this";
}
}
-
Above code is running well but i dont know how to implementing the "session" part in HTTP. I'm expecting something like put a token or something in header or url in real life application/implementation not in the testing environment. How the client get the token? How do we call the request (with token embedd) in client code.?
Are you looking for mocking a session object.If yes then you need to import the mock session object, and in the test class you can create and use the object.
import org.springframework.mock.web.MockHttpSession;
MockHttpSession session = new MockHttpSession();
session.setAttribute("variable", object);
The configuration you have will use the server side session to maintain the security context, and the link with the client is the standard servlet JSESSIONID cookie, so this has nothing to do with Spring Security. Whether you actually want a session or not will depend on the nature of your client. If there is no state maintained between the client and server, then each request from the client must be separately authenticated/authorized. This might be done using Basic authentication for example, or something like an OAuth2 access token depending on your requirements.