Spring webflow unit test with spring security on - spring

I'm trying to test a webflow controller with spring security on:
<action-state id="search">
<secured attributes="ROLE_ADMIN"/>
...
</action-state>
I'm using AbstractXmlFlowExecutionTests subclass.
Now, the test runs ok without the "secured" tag (I don't make any mocks for the security), but once I add the security tag, the test keeps finishing with success, although I anticipate a security exception to be thrown.
Any ideas why it doesn't work and how should I configure it?
Thanks in advance!
Igor

Ok, I've found the solution: I needed to add a securityListener manually.
Before startFlow:
setFlowExecutionListener(getSecurityListener(new String[] {"ROLE_ADMIN_FAKE"}));
Where
private FlowExecutionListener getSecurityListener(String[] roles) {
List<GrantedAuthority> result = new ArrayList<>();
for (String role: roles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role);
result.add(authority);
}
Authentication auth = new PreAuthenticatedAuthenticationToken("Igor", "", result);
SecurityContextHolder.getContext().setAuthentication(auth);
return new SecurityFlowExecutionListener();
}

Related

How do you get the Request URL in spring Boot from an AuthorizationFailureEvent

We are using Spring Boot 2.5.2 with Keycloak 14.0. I am trying to log the authorization events in addition to the URL the user called.
I am trying to follow Spring Boot Authentication Auditing Support. The code to retrieve the Request URL is:
private void onAuthorizationFailureEvent(
AuthorizationFailureEvent event) {
Map<String, Object> data = new HashMap<>();
data.put(
"type", event.getAccessDeniedException().getClass().getName());
data.put("message", event.getAccessDeniedException().getMessage());
data.put(
"requestUrl", ((FilterInvocation)event.getSource()).getRequestUrl() );
if (event.getAuthentication().getDetails() != null) {
data.put("details",
event.getAuthentication().getDetails());
}
publish(new AuditEvent(event.getAuthentication().getName(),
AUTHORIZATION_FAILURE, data));
}
When I attempt this I am getting a ClassCastException when calling event.getSource(). The source seems to be a ReflectiveMethodInvocation (to my Controller) and not a FilterInvocation. Can anybody explain this? How do I get the request url?

Spring Boot Webflux Security - reading Principal in service class when writing tests

I am quite new to the Spring ecosystem in general and Webflux. There are 2 things that I am trying to figure out and cannot find any specifics about.
My Setup:
I am writing a Spring Boot 2 REST API using WebFlux (not using controllers but rather handler functions). The authentication server is a separate service which issues JWT tokens and those get attached to each request as Authentication headers. Here is a simple example of a request method:
public Mono<ServerResponse> all(ServerRequest serverRequest) {
return principal(serverRequest).flatMap(principal ->
ReactiveResponses.listResponse(this.projectService.all(principal)));
}
Which i use to react to a GET request for a list of all "Projects" that a user has access to.
I afterwards have a service which retrieves the list of projects for this user and i render a json response.
The Problems:
Now in order to filter the projects based on the current user id i need to read it from the request principal. One issue here is that i have plenty service methods which need the current user information and passing it through to the service seems like an overkill. One solution is to read the principal inside the service from:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Question 1:
Is this a good practice in general when writing functional code (If i do this instead of propagating the principal)? is this a good approach despite the complexity of reading and sending the principal from the request to the service in each method?
Question 2:
Should i instead use the SecurityContextHolder Thread Local to fetch the principal, and if i do that how do i write tests for my service?
If i use the Security Context how do i test my service implementations which are expecting a principal that is of type JWTAuthenticationToken
and i always get null when trying to do something like described here: Unit testing with Spring Security
In the service tests, In tests what i've managed to do so far is to propagate the principal to the service methods and use mockito to mock the principal. This is quite straightforward.
In the Endpoint Tests i am using #WithMockUser to populate the principal when doing requests and i mock out the service layer. This has the downside of the principal type being different.
Here is how my test class for the service layer looks:
#DataMongoTest
#Import({ProjectServiceImpl.class})
class ProjectServiceImplTest extends BaseServiceTest {
#Autowired
ProjectServiceImpl projectService;
#Autowired
ProjectRepository projectRepository;
#Mock
Principal principal;
#Mock
Principal principal2;
#BeforeEach
void setUp() {
initMocks(this);
when(principal.getName()).thenReturn("uuid");
when(principal2.getName()).thenReturn("uuid2");
}
// Cleaned for brevity
#Test
public void all_returnsOnlyOwnedProjects() {
Flux<Project> saved = projectRepository.saveAll(
Flux.just(
new Project(null, "First", "uuid"),
new Project(null, "Second", "uuid2"),
new Project(null, "Third", "uuid3")
)
);
Flux<Project> all = projectService.all(principal2);
Flux<Project> composite = saved.thenMany(all);
StepVerifier
.create(composite)
.consumeNextWith(project -> {
assertThat(project.getOwnerUserId()).isEqualTo("uuid2");
})
.verifyComplete();
}
}
Based on the other answer, i managed to solve this problem in the following way.
I added the following methods to read the id from claims where it normally resides within the JWT token.
public static Mono<String> currentUserId() {
return jwt().map(jwt -> jwt.getClaimAsString(USER_ID_CLAIM_NAME));
}
public static Mono<Jwt> jwt() {
return ReactiveSecurityContextHolder.getContext()
.map(context -> context.getAuthentication().getPrincipal())
.cast(Jwt.class);
}
Then i use this within my services wherever needed, and i am not forwarding it through the handler to the service.
The tricky part was always testing. I am able to resolve this using the custom SecurityContextFactory. I created an annotation which i can attach the same way as #WithMockUser, but with some of the claim details i need instead.
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockTokenSecurityContextFactory.class)
public #interface WithMockToken {
String sub() default "uuid";
String email() default "test#test.com";
String name() default "Test User";
}
Then the Factory:
String token = "....ANY_JWT_TOKEN_GOES_HERE";
#Override
public SecurityContext createSecurityContext(WithMockToken tokenAnnotation) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
HashMap<String, Object> headers = new HashMap<>();
headers.put("kid", "SOME_ID");
headers.put("typ", "JWT");
headers.put("alg", "RS256");
HashMap<String, Object> claims = new HashMap<>();
claims.put("sub", tokenAnnotation.sub());
claims.put("aud", new ArrayList<>() {{
add("SOME_ID_HERE");
}});
claims.put("updated_at", "2019-06-24T12:16:17.384Z");
claims.put("nickname", tokenAnnotation.email().substring(0, tokenAnnotation.email().indexOf("#")));
claims.put("name", tokenAnnotation.name());
claims.put("exp", new Date());
claims.put("iat", new Date());
claims.put("email", tokenAnnotation.email());
Jwt jwt = new Jwt(token, Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS), headers,
claims);
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, AuthorityUtils.NO_AUTHORITIES); // Authorities are needed to pass authentication in the Integration tests
context.setAuthentication(jwtAuthenticationToken);
return context;
}
Then a simple test will look like this:
#Test
#WithMockToken(sub = "uuid2")
public void delete_whenNotOwner() {
Mono<Void> deleted = this.projectService.create(projectDTO)
.flatMap(saved -> this.projectService.delete(saved.getId()));
StepVerifier
.create(deleted)
.verifyError(ProjectDeleteNotAllowedException.class);
}
As you are using Webflux you should be using the ReactiveSecurityContextHolder to retrieve the principal like so : Object principal = ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal();
The use of the non-reactive one will return null as you are seeing.
There is more info related to the topic in this answer - https://stackoverflow.com/a/51350355/197342

How to implement Session Tracking in spring MVC?

I am very new to spring mvc, I have to develop a web application based on session tracking and my application is annotation based. In my web app I have route each page based on the username and role existence in session. Initially I have been using HttpSession as parameter to controller method, but it is very difficult to check each and every request. I know there are many application level security ways in spring, but I really couldn't understand how to use them. Please suggest me some solutions, For all help thanks in advance.
After updating with interceptors:
Controller class
// Method to showLogin page to user
#RequestMapping(value = "user")
public ModelAndView showLoginToUser(#ModelAttribute("VMFE") VmFeUser VMFE,HttpSession session) {
System.out.println("#C====>showLoginToUser()===> ");
ModelAndView view = new ModelAndView();
//session.setAttribute("user_name", "no_user");
try {
view.setViewName("login");
} catch (Exception e) {
e.printStackTrace();
}
return view;
}
Interceptor
public class HelloWorldInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle (HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
RequestMapping rm = ((HandlerMethod) handler).getMethodAnnotation(
RequestMapping.class);
boolean alreadyLoggedIn = request.getSession()
.getAttribute("user_name") != null;
boolean loginPageRequested = rm != null && rm.value().length > 0
&& "login".equals(rm.value()[0]);
if (alreadyLoggedIn && loginPageRequested) {
//response.sendRedirect(request.getContextPath() + "/app/main-age");
return false;
} else if (!alreadyLoggedIn && !loginPageRequested) {
System.out.println("REDIRECTING===");
response.sendRedirect(request.getContextPath() + "/user");
return false;
}
return true;
}
}
Using spring security you can implement session tracking and apply filters to validate requests. Spring security is very easy to implement. Kindly follow spring security tutorial click here.
You can also check my git repo for implementation click here. It's a angular spring boot application and i have used spring security and JWT for authentication and authorization.
Hope it helps you thanks.

Grails Spring Security AbstractPreAuthenticatedProcessingFilter redirect

I am trying to do PreAuthentication using Spring Security Grails plugin. I read the pre authentication documentation given below, but could not find anything concrete for my situation
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/preauth.html
In my situation, we have a agent which parses the SAML request and gives a map after successful authentication. Recommendation is to use this jar. Hence, I extended AbstractPreAuthenticatedProcessingFilter and try to do this
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
userInfo = agent.readToken(request);
if (!userInfo){
WebUtils.retrieveGrailsWebRequest().getCurrentResponse().sendRedirect(ssoUrl)
}
return userInfo
}
I have placed myFilter under src/groovy and registered this filter in BootStrap
def init = { servletContext ->
SpringSecurityUtils.clientRegisterFilter(
'myFilter', SecurityFilterPosition.PRE_AUTH_FILTER.order)
}
Its getting loaded correctly, but filter is not issuing a redirect. First of all, I wanted to check if this is the right approach and if it is, how to get redirect working.
I have asked the same question in grails user forum
Any help is greatly appreciated.
Update:
Final configuration which worked for me
Wrote MyAuthenticationService which implements AuthenticationUserDetailsService as suggested. You also have to define preAuthenticatedAuthenticationProvider which wraps your custom service
resources.groovy
securityFilter(MySSOAuthFilters){ bean ->
authenticationManager = ref('authenticationManager')
grailsApplication = ref('grailsApplication')
}
customUserDetailsService(MyAuthenticationService)
preAuthenticatedAuthenticationProvider(org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider) {
preAuthenticatedUserDetailsService = ref('customUserDetailsService')
}
I was not able to do <form-login> because in Grails, this is done using
grails.plugins.springsecurity.auth.loginFormUrl config parameter which only accepts relative url.
What I ended up doing is grails.plugins.springsecurity.auth.loginFormUrl = '/login/index'
In LoginController
def index() {
if(springSecurityService.isLoggedIn()){
log.info("User is logged in")
return redirect(controller: 'mycontroller', action: 'list')
}
log.info("user is not logged in...redirect to sso.")
return redirect(url: ssoUrl)
}
Hope this helps
A couple of things I see that need to be changed.
First, do not to send a redirect in the preauth filter just simply return null. The preauth filter is only used to return a subject that your AuthenticationUserDetailsService can use to create the UserDetails object by implementing the method below in your implementation of the AuthenticationUserDetailsService .
public UserDetails loadUserDetails(AbstractAuthenticationToken token) {
return createUserFromSubject((Subject) token.getPrincipal());
}
Second, set the form login page as part of your configuration. This will be used to redirect to if no subject exists.
<form-login login-page="http://url_youwanttoredirect_to_on_auth_req" authentication-failure-url="http://url_youwanttoredirect_to_on_auth_req"/>

Authentication in Spring MVC via REST

I've been looking for a way to authenticate a user via REST controller (URL params).
The closest thing to do so is the following:
#Controller
#RequestMapping(value="/api/user")
public class UserController extends BaseJSONController{
static Logger sLogger = Logger.getLogger(UserController.class);
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody String login(#RequestParam(value="username") String user, #RequestParam(value="password") String pass) throws JSONException {
Authentication userAuth = new UsernamePasswordAuthenticationToken(user, pass);
MyCellebriteAuthenticationProvider MCAP = new MyCellebriteAuthenticationProvider();
if (MCAP.authenticate(userAuth) == null){
response.put("isOk", false);
}
else{
SecurityContextHolder.getContext().setAuthentication(userAuth);
response.put("isOk", true);
response.put("token", "1234");
}
return response.toString();
}
}
However, this doesn't create a cookie.
Any idea or a better way to implement what I want to achieve?
Firstly, you should not do this manually:
SecurityContextHolder.getContext().setAuthentication(userAuth)
It is better to employ special filter responsible for authentication, setting security context and clearing it after request is handled. By default Spring Security uses thread locals to store security context so if you don't remove it after client invocation, another client can be automatically logged in as someone else. Remember that server threads are often reused for different request by different clients.
Secondly, I would recommend using basic or digest authentication for your RESTful web service. Both are supported by Spring Security. More in docs http://static.springsource.org/spring-security/site/docs/3.1.x/reference/basic.html
And finally, remember that RESTful web service should be stateless.
Also remember that Spring Security documentation is your friend. :-)

Resources