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. :-)
Related
I'm trying to configure Spring SAML to work with multiple ACS URLs. I'd like the ACS URL to be determined based on some input the user provides, and it will select one of two ACS urls.
For example:
The user passes in a value A in the request, the ACS URL will be http://server1.com/saml/response.
The user passes in a value B in the request, the ACS URL will be http://server2.com/saml/response in the SAML Response
Any ideas or pointers in the right direction would be appriciated.
You don't specify what version of Spring Security SAML you're using. This is an example based on 1.0.10.RELEASE and is available here.
This is one way to do it:
public class ConfigurableWebSsoProfile extends WebSSOProfileImpl {
#Override
protected AuthnRequest getAuthnRequest(final SAMLMessageContext context,
final WebSSOProfileOptions options,
final AssertionConsumerService acs,
final SingleSignOnService bindingService)
throws SAMLException, MetadataProviderException {
AuthnRequest request = super.getAuthnRequest(context, options,
acs, bindingService);
if (something == true) {
request.setAssertionConsumerServiceURL(...);
} else {
request.setAssertionConsumerServiceURL(...);
}
return request;
}
}
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
I have few Spring-boot controller classes to expose few rest web-services. Whenever some user tries to access any of those services, I need to invoke an web-service to check whether the user (user id will be passed as RequestHeader) is authorized or not. If not authorised, need to display an error page (freemarker template) to the user.
I don't want to write a method which will invoke the authentication webservice and call that from each controller methods and throw an exception and redirect the user to the access denied error page using #ControllerAdvice as here I have to call the method from all controller methods.
I'm not sure whether I can use WebSecurityConfigurerAdapter/AuthenticationManagerBuilder to call the webservice and do the validation.
I'm looking for some solution where I would write an interceptor and spring-boot will invoke the webservice before calling the controller classes and will be able to redirect to the error page, if validation fails.
As a recommendation, take a few minutes for reading about Spring Security (https://projects.spring.io/spring-security/), you must configure it and probably you will spend more time than expected, anyway you have so much more profits than make security by ourself.
Benefits are things like:
#PreAuthorize("hasRole('ROLE_USER')")
On every place you can get the user logged through the SecurityContext with something like:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
The way SpringSecurity authenticate users is with JWT (JsonWebToken) this is a really nice way because you can pass and retrieve all information you want:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
And you can forget every possible problem (bad authentication, phishing, xss or csrf..) because it works with public/private key and secrets, so anyone can create a token.
Apologies if my question seems like something a novice would ask. I am new to the Spring world. I am using Spring Security to authenticate users. Authentication is working properly, but after authentication success I want Spring to call the Controller method .`
#RequestMapping(value = "/login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public JwtAuthenticationResponse userLogin() {
System.out.println("Login Success");
JwtUser user = (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String token = userService.generateToken(authenticationService.loadUserByUsername(user.getUsername()));
JwtAuthenticationResponse response = new JwtAuthenticationResponse(token, user.getAuthorities().toArray(),user.getUsername());
return response;
}
}
In Spring Security Configuration I added the following
http.authorizeRequests().anyRequest().permitAll().and().formLogin().loginProcessingUrl("/login").defaultSuccessUrl("/login");
http.addFilterBefore(filter,UsernamePasswordAuthenticationFilter.class);
In this case, it is returning a default Spring Login form. It is not calling my controller method.
I made the request using
http://localhost:8080/myapp/login
Can someone suggest what I have to do to invoke the Controller after login is successful, in order to send the Authentication token after login.
Appreciate any help!
Thank you
If you are providing your custom controller for login then just remove following
.formLogin().loginProcessingUrl("/login").defaultSuccessUrl("/login")
From httpSecurity configuration. And this will remove spring security's default login page.
or you can follow : http://docs.spring.io/spring-security/site/docs/current/guides/html5/form-javaconfig.html
I found the mistake I was doing. I need not declare request mapping for login. I realized that Spring will take care of it.
How can I configure a grails application using Spring security such that one set of url's will redirect unauthenticated users to a custom login form with an http response code of 200, whereas another set of url's are implementing restful web services and must return a 401/not authorized response for unauthenticated clients so the client application can resend the request with a username and password in response to the 401.
My current configuration can handle the first case with the custom login form. However, I need to configure the other type of authentication for the restful interface url's while preserving the current behavior for the human interface.
Thanks!
If I understood right what you want to do, I got the same problem, before! but it is easy to solve it using Spring Security grails Plugin! So, first of all, you have to set your application to use basic authentication:
grails.plugins.springsecurity.useBasicAuth = true
So your restful services will try to login, and if it doesnt work it goes to 401!
This is easy but you also need to use a custom form to login right?! So you can just config some URL to gets into your normal login strategy like this:
grails.plugins.springsecurity.filterChain.chainMap = [
'/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter',
'/**': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]
So noticed, that above, everything that comes to the URL /api/ will use the Basic Auth, but anything that is not from /api/ uses the normal authentication login form!
EDIT
More information goes to http://burtbeckwith.github.com/grails-spring-security-core/docs/manual/guide/16%20Filters.html
I had the same issue and did not found a good solution for this. I am really looking forward a clean solution (something in the context like multi-tenant).
I ended up manually verifying the status and login-part for the second system, which should not redirect to the login page (so I am not using the "Secured" annotation). I did this using springSecurityService.reauthenticate() (for manually logging in), springSecurityService.isLoggedIn() and manually in each controller for the second system. If he wasn't, I have been redirecting to the specific page.
I do not know, whether this work-around is affordable for your second system.
You should make stateless basic authentication. For that please make following changes in your code.
UrlMappings.groovy
"/api/restLogin"(controller: 'api', action: 'restLogin', parseRequest: true)
Config.groovy
grails.plugin.springsecurity.useBasicAuth = true
grails.plugin.springsecurity.basic.realmName = "Login to My Site"
grails.plugin.springsecurity.filterChain.chainMap = [
'*' : 'statelessSecurityContextPersistenceFilter,logoutFilter,authenticationProcessingFilter,customBasicAuthenticationFilter,securityContextHolderAwareRequestFilter,rememberMeAuthenticationFilter,anonymousAuthenticationFilter,basicExceptionTranslationFilter,filterInvocationInterceptor',
'/api/': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]
resources.groovy
statelessSecurityContextRepository(NullSecurityContextRepository) {}
statelessSecurityContextPersistenceFilter(SecurityContextPersistenceFilter, ref('statelessSecurityContextRepository')) {
}
customBasicAuthenticationEntryPoint(CustomBasicAuthenticationEntryPoint) {
realmName = SpringSecurityUtils.securityConfig.basic.realmName
}
customBasicAuthenticationFilter(BasicAuthenticationFilter, ref('authenticationManager'), ref('customBasicAuthenticationEntryPoint')) {
authenticationDetailsSource = ref('authenticationDetailsSource')
rememberMeServices = ref('rememberMeServices')
credentialsCharset = SpringSecurityUtils.securityConfig.basic.credentialsCharset // 'UTF-8'
}
basicAccessDeniedHandler(AccessDeniedHandlerImpl)
basicRequestCache(NullRequestCache)
basicExceptionTranslationFilter(ExceptionTranslationFilter, ref('customBasicAuthenticationEntryPoint'), ref('basicRequestCache')) {
accessDeniedHandler = ref('basicAccessDeniedHandler')
authenticationTrustResolver = ref('authenticationTrustResolver')
throwableAnalyzer = ref('throwableAnalyzer')
}
CustomBasicAuthenticationEntryPoint.groovy
public class CustomBasicAuthenticationEntryPoint extends
BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
ApiController
#Secured('permitAll')
class ApiController {
def springSecurityService
#Secured("ROLE_USER")
def restLogin() {
User currentUser = springSecurityService.currentUser
println(currentUser.username)
}
}