Looking to protect pages in a basic java spring application based on a token. After the token is consumed I would need the application to know the token was valid at some point and then put some time to live on that session. Below is the controller I have to consume the token.
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(ModelMap model, #RequestParam(value = "token", required = false) String token) {
if(token==null) {
return "redirect:403";
} else if(token.isEmpty()) {
return "redirect:403";
} else {
//perform token WS call to validate the token
return "redirect:home";
}
}
#RequestMapping(value = "/403", method = RequestMethod.GET)
public ModelAndView accesssDenied(Principal user) {
ModelAndView model = new ModelAndView();
model.addObject("msg",
"You do not have permission to access this page!");
model.setViewName("403");
return model;
}
After performing some check on the token how can I protect all of the subsequent pages? Id also like to be able to secure api calls as well. Can anyone point me in the direction of the spring component?
I think you should take a look at Spring Security instead of rolling your own solution - it is built for handling authentication.
What you especially should look at is session management which sounds like what you're trying to do here.
Depending on how your users get their token you might have to implement your own authentication manager and/or login flow, though the default ones cover a lot of common cases too.
Once you have Spring Security set up and your session management working you would protect URLs either by annotating the controller methods:
#RequestMapping("/api/protected")
#PreAuthorize("hasRole('ROLE_USER')")
public String myProtectedController(Authentication authentication, Model model) {
// User will be authenticated here
}
or by registering them into the HTTP security configuration:
#Configuration
public class SecurityConfig extends WebSecurityConfigurationAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// Everyone can acess /login
.antMatchers("/login").permitAll()
// Only authorized users can access URLs under /api/
.antMatchers("/api/**").access("hasRole('ROLE_USER')")
}
}
Of course, in your case you might use something other than ROLE_USER since you may or may not have actual users but something else in your session that you can use.
Related
I'm writing a simple REST API using Spring Boot and I want to enable basic authentication. Therefore I have used the WebSecurityConfigurerAdapter as shown below. For simplicity, I just want to check only the password (pwd123) and allow any user to log in. Please refer to the code below.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null || authentication.getCredentials() == null) {
throw new BadCredentialsException("Bad credentials");
}
if (authentication.getCredentials().equals("pwd123")) {
return new UsernamePasswordAuthenticationToken(authentication.getName(),
authentication.getCredentials().toString(),
Collections.emptyList());
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
});
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic();
}
}
Assume user_A has accessed the REST API with a valid password, i.e pwd123, and then do the send API call with a wrong password. However the user is allowed to access the API which is the problem.
When I do the debugging I realized that authenticationIsRequired function in BasicAuthenticationFilter class which is in Spring Security, returns false in such scenario. Please refer that code.
private boolean authenticationIsRequired(String username) {
// Only reauthenticate if username doesn't match SecurityContextHolder and user
// isn't authenticated (see SEC-53)
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if (existingAuth == null || !existingAuth.isAuthenticated()) {
return true;
}
// Limit username comparison to providers which use usernames (ie
// UsernamePasswordAuthenticationToken) (see SEC-348)
if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
return true;
}
// Handle unusual condition where an AnonymousAuthenticationToken is already
// present. This shouldn't happen very often, as BasicProcessingFitler is meant to
// be earlier in the filter chain than AnonymousAuthenticationFilter.
// Nevertheless, presence of both an AnonymousAuthenticationToken together with a
// BASIC authentication request header should indicate reauthentication using the
// BASIC protocol is desirable. This behaviour is also consistent with that
// provided by form and digest, both of which force re-authentication if the
// respective header is detected (and in doing so replace/ any existing
// AnonymousAuthenticationToken). See SEC-610.
return (existingAuth instanceof AnonymousAuthenticationToken);
}
Please let me know what is missing in my implementation
As mentioned in the comments, instead of providing a custom AuthenticationProvider you can try providing a custom UserDetailsService. Here's the complete configuration:
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests((authorizeRequests) -> authorizeRequests
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
#Bean
public UserDetailsService userDetailsService() {
return (username) -> new User(username, "{noop}pwd123", AuthorityUtils.createAuthorityList("ROLE_USER"));
}
}
When you evolve to looking up the user via a third-party service, you can add the code to do this in the custom UserDetailsService (a lambda function or an actual class that implements the interface) and continue returning a org.springframework.security.core.userdetails.User.
Note: I don't actually recommend plain-text passwords in production. You would replace {noop}pwd123 with something like {bcrypt}<bcrypt encoded password here>.
As suggested in the comments and answers, even if you use the InMemoryUserDetailsManager the problem does not get resolved, which means, once the user is authenticated with the correct user name and password, his password is not validated in the subsequent REST API calls,i.e. can use any password. This is because of the functionality in BasicAuthenticationFilter class where it skips users who are having a valid JSESSION cookie.
To fix the issue, we should configure http to create state-less sessions via
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
in configure function of the WebSecurityConfigurerAdapter
Please refer Why BasicAuthenticationFilter in spring security matches only username and not the password
This is my first Question ever here on SO, it was helpfull and saved me lots of time, but now I can't find any solution to my problem.
As I'm rather new to spring and espacially to spring-security, I'm stuck with something that might be easy if i had more knowledge.
I have an existing Application that uses a local user database. It uses a custom UserDetails implementation that works if used with user:password authentification through a login form.
Here is the current setup:
public class SecurityContext extends WebSecurityConfigurerAdapter {
....
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider result = new DaoAuthenticationProvider();
result.setUserDetailsService(userDetailsService());
result.setPasswordEncoder(passwordEncoder());
return result;
}
#Override
#Bean
public GatesUserDetailsService userDetailsService() {
GatesUserDetailsService result = new GatesUserDetailsService();
result.setClientService(clientService);
result.setAccountService(accountService);
result.setCardService(cardService);
result.setPersonService(personService);
result.setAccountPropertyService(accountPropertyService);
result.setLoginAttemptService(loginAttemptService);
return result;
}
Now I want to use SSO from an external IDP that speaks OpenIdConnect.
Going through the documentation I was able to get this up and running in a "default" manner. That is, at the and of my process a get a user that is an Instance of OidcUser. I need that user to be either extended or incorporate the existing userDetails.
The documentation (Spring Boot and OAuth2) recommends to
Implement and expose OAuth2UserService to call the Authorization
Server as well as your database. Your implementation can delegate to
the default implementation, which will do the heavy lifting of calling
the Authorization Server. Your implementation should return something
that extends your custom User object and implements OAuth2User.
I was able to introduce my own Oauth2UserService that gets called right at the and of the authentification by setting:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.oauth2Login()
.failureHandler(authenticationFailureHandler())
.successHandler(authenticationSuccessHandler())
.userInfoEndpoint()
.userService(this.oauth2UserService())
.oidcUserService(this.oidcUserService());}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
OidcUser oidcUser = delegate.loadUser(userRequest);
//..DO some additional Stuff check against external Server
//Here I could load my custom userDetails
GatesUserDetails userDetails = (GatesUserDetails) userDetailsService.loadUserByUsername("131:" + username);
....
But I have now Idea how to make my customUser a vaild return to my function.
I tried to implement the OidcUser Interface in my userDetails, but still it does not work.
Any hint (even to a more understandable doc) would be highly appreciated.
EDIT
To clarify things, I implemented the oidcUser Interface as stated in the docs along with the necessary implementations (getAttribute, getAttributes, getAuthorities) but still I could not use this as the return type would still be our GatesUserDetails, no way (for me) to cast it to oidcUser
Have the same problem with spring-security-oauth2-client-5.6.2, after hours google and debugger it solved.
First, make sure your UserInfo entrypoint is correct in case you own the
Auth server.
Plus requested scopes contains any of profiles not
only openid.
Logic found here: OidcUserService::shouldRetrieveUserInfo
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
// Auto-disabled if UserInfo Endpoint URI is not provided
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
if (StringUtils.isEmpty(providerDetails.getUserInfoEndpoint().getUri())) {
return false;
}
// The Claims requested by the profile, email, address, and phone scope values
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
// when a response_type value is used that results in an Access Token being
// issued.
// However, when no Access Token is issued, which is the case for the
// response_type=id_token,
// the resulting Claims are returned in the ID Token.
// The Authorization Code Grant Flow, which is response_type=code, results in an
// Access Token being issued.
if (AuthorizationGrantType.AUTHORIZATION_CODE
.equals(userRequest.getClientRegistration().getAuthorizationGrantType())) {
// Return true if there is at least one match between the authorized scope(s)
// and accessible scope(s)
return this.accessibleScopes.isEmpty()
|| CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes);
}
return false;
}
Hope this could help someone.
Based on my understanding, there are a number of different ways to retrieve the authenticated username in Spring Security.
I'm currently grabbing the username by included the Principal as a controller method argument:
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Principal principal) {
modelAndView.addObject("email", principal.getName());
// Render template located at src/main/resources/templates/dashboard.html
modelAndView.setViewName("dashboard");
return modelAndView;
}
Does Spring Security offer an easy way for me to store the User object into the session so it can be easily retrieved by any controller method?
I want to avoid performing a DB lookup each time:
// Lookup user in database by e-mail
User user = userService.findUserByEmail(principal.getName());
I'm using Spring Security 4.2.
Spring Security provides you with a static method for quickly and easy access:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
Or
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = user.getUsername();
Maybe you would like do this in a base abstract class
public abstract class BaseController {
protected User getCurrentUser() {
return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
...
public YourController extends BaseController {
...
}
Update
If you want to store the current authenticated user in session, then you need store only first time in a object as suggested by #gkatzioura.
#Component
#Scope("session")
public class MySessionInfo {
private User user;
protected User getCurrentUser() {
if (user == null) {
user = userService.findUserByEmail(SecurityContextHolder.getContext().getAuthentication().getPrincipal().getName());
}
return user;
}
}
You can inject this bean in yours controllers like
#Autowired
private MySessionInfo mySessionInfo;
You must take care about cases when user is not logged, but this is another problem.
You can always use the methods that spring security provides to get basic information such as name, authorities and everything provided by the Authentication.class.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getAuthorities();
authentication.getName();
But if you want more information, using a session bean to store the information is also a good idea.
#Component
#Scope("session")
public class UserInfo { .. }
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.
I have a Spring bean that needs information from the request, but isn't directly called from the controller (although it could be - but I'd like to try this without it)
Basically, my API makes requests to other services over thrift. When it makes the request, there's a service call like this:
authenticationService.authenticate(null, "username", "password");
The first parameter (the null) is usually a "placeholder" instance of a request context. The request context contains information about the user making the request, the originating IP, etc. This way, I get all of the details about the original caller without letting my API infrastructure leak into the backend.
However, to do this, I have an InvocationHandler that intercepts method calls made against a proxy of my service interfaces. Inside of that proxy handler, I have a RequestContextFactory wired in that creates instances of a RequestContext. Inside of this factory, I need to get information from the request. Particularly, the SecurityContext, so I can identify the user making the call.
Right now, I have:
#Provider
#Component
public class WebRequestContextFactory implements RequestContextFactory {
#Context private ContainerRequest containerRequest;
public RequestContext createRequestContext() {
}
}
Unfortunately, containerRequest is always null.
You can use ServletRequestAttributes to get the information from the request and the ServletRequestAttributes can be obtained from RequestContextHolder:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes();
If the request is processed by the Spring DispatcherServlet, there is no need of any special setup. DispatcherServlet already expose all relevant state. But if the requests are processed outside of Spring's DispatcherServlet, then you need to add javax.servlet.ServletRequestListener in your application's web.xml file:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
This will associate the request with the current thread and the associated request attributes can then be retrieved via RequestContextHolder.
In a slightly different scenario, the following worked for me. YMMV.
Replace this:
#Context private ContainerRequest containerRequest;
With this:
#Context private javax.inject.Provider<ContainerRequest> containerRequestProvider;
Then at the place in your code where you need the ContainerRequest:
ContainerRequest containerRequest = containerRequestProvider.get();
Code sample that pointed me toward this solution: https://github.com/psamsotha/jersey-okhttp-interceptor-demo/blob/b3b2da00b284e75011ea780fb37b555ea581ac96/src/main/java/com/example/stackoverflow/client/UserFactory.java
For Authentication, it's better to use container realm, or to use normal servlet.
And for Authorization, you can use Application or rest servlet. And in this kind of process, you can find the info from context annotation. Here's sample:
(#Context final SecurityContext sc, #Context Request request) {
logMe(sc);
...
}
private void logMe(final SecurityContext sc) {
try {
LOGGER.info("User=" + sc.getUserPrincipal().getName());
LOGGER.info("User Role?=" + sc.isUserInRole("user"));
LOGGER.info("Auth way=" + sc.getAuthenticationScheme());
} catch (final Exception e) {
LOGGER.debug(e);
}
}
Or:
(#Context final SecurityContext sc, #Context ContainerRequestContext request) {
...
You can create a access token that contains your desire information such as IP Address, user name etc. During the authentication phase create a custom token and put this token into spring security context. Later you could extract this token from other places such in your proxy classes. After extracting the token you validate or whatever you want.
Creating custom object and token:
public class CustomAuthentication {
private String userId;
private String password;
private String ipAddress;
}
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private CustomAuthentication customAuthentication;
public CustomAuthenticationToken(MobiLabAuthentication authentication,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.customAuthentication = authentication;
setAuthenticated(true);
}
public CustomAuthenticationToken() {
super(null);
setAuthenticated(false);
}
#Override
public Object getCredentials() {
return customAuthentication.getPassword();
}
#Override
public Object getPrincipal() {
return customAuthentication.getUserId();
}
}
Store the token into Spring security context
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new RestUserAuthrity("YOUR_APP_ROLE"));
//Extract IP , user and pass etc and construct CustomAuthentication instance
CustomAuthentication authentication = new CustomAuthentication(.....)
CustomAuthenticationToken authenticationToken = new CustomAuthenticationToken(
authentication, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
Validate security information from the Proxy bean
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
if (authentication instanceof CustomAuthenticationToken) {
CustomAuthenticationToken token = (CustomAuthenticationToken) authentication;
//now you can get your ip address from token
}