I am using spring & jersey2 to serve some rest-requests like:
#GET
#Path("/someservice")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String getSomeStuff(...) {
login(...);
// ...
}
During a rest-request, I get an authorized user of the rest-request.
Now I need this user while updating or creating entities like:
#MappedSuperclass
public abstract class PersistentObject {
#PrePersist
#PreUpdate
public void onSaveOrUpdate() {
setCreationUser(...); // How to get the user of this session?
}
// ...
}
How can I get the current user of the rest-request there?
You can try to perform your login operation (for appropriate resource methods) in a ContainerRequestFilter and set SecurityContext:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext context) throws IOException {
final Principal user = login(...);
context.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return user;
}
// Other methods omitted.
});
}
}
Make sure you have jersey-spring3 module on your class-path and the Jersey-Spring integration allows you to inject SecurityContext into a Spring service:
#Service
public MySpringService implements MyService {
#Context
private SecurityContext context;
public String doStuff() {
final Principal user = context.getUserPrincipal();
// ...
}
}
You can't do this if the service, you want to use user principal in, is neither managed by Jersey nor Spring.
Spring Security might be useful to you in two ways:
It can manage authentication, (you would not need to do that login(...) call yourself, it would be done automatically by Spring Security filter chain. But you can still do it manually if you want.
Once a request has been authenticated, as long as the request is alive you can access the authenticated user from anywhere just by doing:
Authentication auth = SecurityContextHolder.getSecurityContext().getAuthentication();
// auth is an object that holds the authenticated user's data
I think you need some sort of authentication by the fact that you make a login(...) and you want to audit the user afterwards. You might not nedd an authentication form, but you do need authentication. Spring Security is not only for interactive applications, you can set up an authentication filter that does authentication based on cookies, request parameters, client certificates or whatever, all of that without user interaction.
Furthermore, Spring Security is very extensible, if you have your authentication method already implemented, integrating with Spring Security is easy. And it is also flexible: you don't need to use the security filter chain if it is too heavyweight for your use case. You can do most things manually and use just a little bit of Spring Security if you want.
I really suggest you take a deeper look at Spring docs about:
Spring Security core components overview and Spring Security authentication overview
I think with just that you will be able to get something working.
Related
I'm new to spring-boot and quite new to web services.
I would like to implement an authorization process in an (abstract) generic RestController which should get the content of the basic-auth. header from the extending controllers and then perform the authorization.
Something like that:
public class GenericController
{
// Constructor
protected GenericController(String basicAuth) throws MalformedURLException, ProtocolException, IOException
{
// check user can execute action
}
}
#RestController
#RequestMapping( "/users" )
public class UserController extends GenericController
{
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
// Constructor
protected UserController() throws MalformedURLException, ProtocolException, IOException
{
// somehow get the basic auth information from header and pass it to the parent's constructor
basicAuth = ???
super(basicAuth);
}
#GetMapping( "/user/{cn}" )
public String getUser( #PathVariable String cn )
{
logger.error("Start getUser(...): cn=" + cn);
}
How can I do that? (is that even possible?)
ADDED INFORMATION:
My web services are themselves consumers of other web services.
Once the calling user is authorized to "use" my web services I have to forward/set the Basic Authentication into the request for the web services I call.
So, how/where can I "intercept" and get the headers when my services are called?
I would recommend to take a look at Spring Security framework. There are many tutorials showing how to configure it to perform basic authentication, e.g.: here.
The main idea is decoupling authentication / authorization concerns from controllers and business logic code. To implement this filters are used which intercept every request and validate it against different conditions. When implementing stateless REST-service (each request independent from others) it's not so difficult. When handling sessions, logic will become much more complicated. Spring Security implements filter chains, consisting of multiple filters, each performing its own checks.
Spring Security is used very often in spring-based projects and is definitely worth to look at, although it may seem a bit difficult when meeting it for the first time.
You can use HttpServletRequest to get the header from the request.
protected UserController(HttpServletRequest httpServletRequest) throws MalformedURLException, ProtocolException, IOException
{
System.out.println( httpServletRequest.getHeaders("key name"));
// somehow get the basic auth information from header and pass it to the parent's constructor
basicAuth = ???
super(basicAuth);
}
I am trying to build an OAuth2 Client using spring boot to access a custom API.
Here is my code so far:
#SpringBootApplication
public class MyClient {
public static void main(String[] args){
SpringApplication.run(MyClient.class, args);
}
}
#Configuration
#EnableOAuth2Sso
public class ApplicationSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.antMatcher("/**")
.authorizeRequests().antMatchers("/","/callback","/login**","/error**")
.permitAll().anyRequest().authenticated();
}
}
#RestController
public class HomeController {
#RequestMapping("/user")
public String login() {
String userInfoJson = "get data returned from API somehow";
return userInfoJson;
}
#RequestMapping("/callback")
public void callback(HttpServletResponse response) {
response.sendRedirect("/user");
}
}
I have created an application.yml file with all the properties needed and the login process works perfectly, returning the flow to /callback after a successful login.
At that point I should have received a token that I can use to fetch the user's data from the server.
How can I access this token?
Also, does spring boot have any classes to do the token validation process automatically, or do I have to create the request manually?
Thanks
from the documentation
An OAuth2 Client can be used to fetch user details from the provider (if such features are available) and then convert them into an Authentication token for Spring Security.
The Resource Server above support this via the user-info-uri property This is the basis for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to participate by providing an annotation #EnableOAuth2Sso.
The Github client above can protect all its resources and authenticate using the Github /user/ endpoint, by adding that annotation and declaring where to find the endpoint (in addition to the security.oauth2.client.* configuration already listed above):
application.yml.
security:
oauth2:
...
resource:
userInfoUri: https://api.github.com/user
preferTokenInfo: false
So as you can read this should be done automatically if you set where the user info shall be fetched from, and then it will be populated.
When an Authentication token is populated you can fetch this from the security context in multiple different ways.
#RequestMapping("/callback")
public void callback(Authentication authentication) {
final String name = authentication.getName();
response.sendRedirect("/user");
}
If you wish to access the raw json, you'll probably have to make the rest call yourself. If you want to add custom values to the authentication object you have to implement your own UserDetails class.
Is it possible to use OAuth2 for certain endpoints in my rest application and use basic authentication too for some other endpoints.
It should all work on spring security version 2.0.1.RELEASE. I hope someone can help me further.
Yes, it's possible to use a basic authentication as well as an OAuth2 authentication intertwined, but I doubt you'll be able to set it up easily as HttpSecurity's authenticated() method doesn't allow you to pick which of your authentication method (oauth2Login/formLogin) will work.
However, there's a way to easily bypass that:
You could add a custom authority, let's call it ROLE_BASICAUTH, when an user connects using basic auth, and ROLE_OAUTH2 when an user connects using OAuth2. That way, you can use
.antMatchers("/endpoint-that-requires-basic-auth").hasRole("BASICAUTH")
.antMatchers("/endpoint-that-requires-oauth2").hasRole("OAUTH2")
.anyRequest().authenticated()
When they reach an endpoint that you want basic authentication (and not OAuth2), you check their current authorities, and if it's not BASICAUTH, then you invalidate their session you display a login form without OAuth2 (to force them to use the basic authentication).
The downside to doing that is that you'd need to implement both a custom UserDetailsService as well as a custom OAuth2UserService...
But that's actually not that hard:
#Service
public class UserService extends DefaultOAuth2UserService implements UserDetailsService {
// ...
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(oAuth2UserRequest);
Map<String, Object> attributes = user.getAttributes();
Set<GrantedAuthority> authoritySet = new HashSet<>(user.getAuthorities());
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
authoritySet.add(new SimpleGrantedAuthority("ROLE_OAUTH2"));
return new DefaultOAuth2User(authoritySet, attributes, userNameAttributeName);
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = getUserFromDatabase(username); // you'll need to provide that method (where are the username/password stored?)
if (user == null) { // UserDetailsService doesn't allow loadUserByUsername to return null, so throw exception
throw new UsernameNotFoundException("Couldn't find user with username '"+username+"'");
}
// add ROLE_BASICAUTH (you might need a custom UserDetails implementation here, because by defaut, UserDetails.getAuthorities() is immutable (I think, I might be a liar)
return user;
}
}
Note that this is a rough implementation, so you'll have to work it out a bit on your end as well.
You can also use this repository I made https://github.com/TwinProduction/spring-security-oauth2-client-example/tree/master/custom-userservice-sample as a guideline for the custom OAuth2UserService
Good luck.
I'm using spring security to implement a programmatic, manual user login. I have a scenario where I have positively established the user's identity, and wish to log them in. I don't know their password, and so can't use the regular login code path where you submit a form to a url, which spring intercepts via a servlet Filter, doing all of it's auth+session magic.
I've searched, and it seems most people create their own Authentication object, and then tell spring about via:
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, "", user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
Indeed, this works. Spring even puts it into the session for me, making subsequent http requests maintain their auth status.
However, I feel like this is a dirty hack. I'll present some details that I hope will give concrete examples of the problems associated with using setAuthentication() inside a controller to achieve a manual login:
To give an idea, my config is:
httpSecurity
.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/admin/**", "/api/admin/**").hasRole("USER_SUPER_ADMIN")
.and()
.formLogin()
.loginPage("/sign-in?sp")
.loginProcessingUrl("/api/auth/sign-in")
.successHandler(createLoginSuccessHandler())
.failureHandler(createLoginFailureHandler())
.permitAll()
.and()
.logout()
.logoutUrl("/api/auth/sign-out")
.logoutSuccessHandler(createLogoutSuccessHandler())
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry)
;
Key points in the above config:
I use custom success and failure handlers for the form login
I want to config behavior for max concurrent sessions per user
I want to maintain spring's default session fixation protection (changing session id upon login).
I want to use a session registry
... more of these session/login functionalities, had I chosen to config it.
I stepped through the code to see how spring processes a form login. As expected, Spring does all the session/login functionalities that my HttpSecurity config told it to do when I use the form login. But, when I do my own custom/manual login via SecurityContextHolder.getContext().setAuthentication(), it does NONE of those functionalities. This is because spring does all of it's session/login functionalities stuff inside of a servlet Filter, and my programmatic code can't really call a Filter. Now, I can attempt to add the missing functionalities myself, duplicating their code: I see that the spring Filter uses: ConcurrentSessionControlAuthenticationStrategy, ChangeSessionIdAuthenticationStrategy, and RegisterSessionAuthenticationStrategy. I can create these objects myself, configure them, and call them after my custom login. But, that's really lame to duplicate all that spring code. Furthermore, there's still other behaviors I'm missing - I noticed that when using the form login code path, that spring triggers some login events which don't get triggered when I do my custom login. And there's probably other stuff that I'm missing or don't understand. The whole process is pretty complicated, and I feel like there's a high chance of introducing bugs if this isn't done right, not to mention that library updates would be a pain if I started duplicating spring code.
So, I feel like I'm approaching this from the wrong way. Should I be using a different strategy, so that I'm not bypassing so much of the stuff that spring does for me? Maybe I should try to make my own AuthenticationProvider to accomplish this custom login?
*To clarify, my code more or less works. But, I feel like I accomplished it using a poor strategy because I had to write code duplicating a lot of stuff that spring does for me. Further, my code doesn't perfectly replicate what spring does, making me wonder what negative implications might result. There must be a better way to programatically achieve login.
I wanted to elaborate on how I implemented the advice of dur. In my scenario, I only used a custom AuthenticationProvider.
Instead of creating a custom servlet Filter, such as extending AbstractAuthenticationProcessingFilter, which seemed like a lot of work, I choose to instead use the following strategy:
At the point in my code where I was confident that I had identified the user, and wanted them to be "logged in", I stuck a flag in the user's session, marking that they should be logged in on the next request, along with any other identity/bookkeeping info I needed, such as their username.
Then, I told the browser client to make an http post to the loginProcessingUrl (the same one I configured spring security to use for form-based login), telling them to send the standard username and password form params, although they don't need to send real values - dummy values like foo are fine.
When the user makes that post request (eg to /login), spring will invoke my custom AuthenticationProvider, which will look in the user's session to check for the flag, and to gather the username. Then it will create and return an Authentication object, such as PreAuthenticatedAuthenticationToken, which identifies the user.
Spring will handle the rest. The user is now logged in.
By doing it this way, you stay within the "normal" way of doing logins, and so spring will still automatically:
Call any custom success and failure handlers you configured for the form login, which is nice if you use that place to do certain things on login, like query or update a db.
It will respect any max concurrent sessions per user settings that you may be using.
You get to keep spring's default session fixation attack protection (changing session id upon login).
If you set a custom session timeout, eg via server.session.timeout in a properties file, spring will use it. There's probably other session config attributes that are done at this time too.
If you enabled spring's "remember me" functionality, it will work.
It will fire a login event, which is used for other spring components, such as storing the user's session in a SessionRegistry. I think the events are also used by other parts of spring, such as the actuator, and for auditing.
When I first tried just doing the typically recommended SecurityContextHolder.getContext().setAuthentication(authentication) to login my user, instead of the custom AuthenticationProvider, none of the above bullets were done for me, which can utterly break your app... or cause subtle security bugs - neither are good.
Here's some code to help solidify what I said:
Custom AuthenticationProvider
#Component
public class AccountVerificationAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AppAuthenticatedUserService appAuthenticatedUserService;
#Autowired
private AuthService authService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// This will look in the user's session to get their username, and to make sure the flag is set to allow login without password on this request.
UserAccount userAccount = authService.getUserAccountFromRecentAccountVerificationProcess();
if (userAccount == null) {
// Tell spring we can't process this AuthenticationProvider obj.
// Spring will continue, and try another AuthenticationProvider, if it can.
return null;
}
// A service to create a custom UserDetails object for this user.
UserDetails appAuthenticatedUser = appAuthenticatedUserService.create(userAccount.getEmail(), "", true);
PreAuthenticatedAuthenticationToken authenticationToken = new PreAuthenticatedAuthenticationToken(appAuthenticatedUser, "", appAuthenticatedUser.getAuthorities());
authenticationToken.setAuthenticated(true);
return authenticationToken;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Config spring security to use the provider
// In your WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class AppLoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccountVerificationAuthenticationProvider accountVerificationAuthenticationProvider;
#Autowired
private ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// Spring will try these auth providers in the order we register them.
// We do the accountVerificationAuthenticationProvider provider first, since it doesn't need to do any slow IO to check,
// so it's very fast. Only if this AuthenticationProvider rejects (which means this http request is not for programmatic login), will spring then try the next AuthenticationProvider in the list.
authenticationManagerBuilder
.authenticationProvider(accountVerificationAuthenticationProvider)
// I'm using ActiveDirectory / LDAP for when a user logs in via entering a user + password via the html form, but whatever you want to use here should work.
.authenticationProvider(activeDirectoryLdapAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
...
}
}
For custom web authentication you should implement combination of a custom authentication filter (for example AbstractAuthenticationProcessingFilter or just GenericFilterBean), a custom authentication provider (AuthenticationProvider) or/and custom authentication token (AbstractAuthenticationToken).
For example, see source of Spring Security Kerberos.
See also:
The AuthenticationManager, ProviderManager and AuthenticationProvider
The setup of the RESPApi project is:
SpringBoot
Spring's OAuth2
In the project we have many clients, so SQL queries almost always have "... and clientId = ?" in the where clause.
We store clientId in the SecurityContext with other user details (we extend Spring's User class).
The question is: how to get the User object in the #Repository?
Possible solutions we can think of:
In every repository implementation add
SecurityContextHolder.getContext().getAuthentication()
cast the result to our custom UserDetails implementation and use it.
Cons: somehow I feel there's a better solution.
Add #AuthenticationPrincipal annotated parameters to the controllers and then pass the parameters to the service layer and then to the repository layer.
Cons: passing the paremeter though 2 layers only to obtain clientId doesn't seem reasonable.
I thought about #Autowired paramter MyUser user in the #Repository class. The first try was to create #Configuration annotated class in which there will be a method
#Bean
public MyUser getUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
return (MyUser) authentication.getPrincipal();
}
}
return null;
}
But the bean is null and I cannot use it.
For now we've ended up with solution nr 1 but I feel there must be a better way.
Any ideas how to solve this problem?
If you're using Spring Data (or have the time to switch to using it), you can use the SecurityEvaluationContextExtension and use principal directly in your queries:
https://stackoverflow.com/a/29692158/1777072
If not, you could hide the static access if it offends (or if you want more control over it changing in future):
#Component
public class AuthenticationHelper {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Then inject that class into your Repository.
Or your Service. That's probably a better fit than the Repository.
I like to keep Repositories dumb (ultimately using Spring Data to avoid writing them entirely).
And I like to think of Services as being separated out of the web layer, running on separate boxes (even if they aren't). In that situation, you would never pass the Authentication details over HTTP from Controller to Service. The service would obtain authentication details for itself, rather than just trusting what the web layer sent it.
So I think the Service should get the details itself, rather than the Controller passing them through.
Your bean is null because by default beans are singleton and they are created when the application starts, and as you can imagine, you are not going to have a SecurityContext at that point.
Try declaring your bean with request scope, in this way:
#Bean
#Scope(value=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyUser getUser() {
.....
}