Using spring security to secure Rest API.
by integrating spring security all goes fine, until I try to develop my own controller which gonna handle the user authentication based on sessionManagement.
Here is my spring security configuration java code :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationEntryPoint authEntryPoint;
#Autowired
private UserDetailsService userDetailsService;
/**
*
* Kind of links provided based on the User authorises USER - SUPER_USER - SIMPLE_USER ..etc
*
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/loginPage").permitAll()
.antMatchers("/securetAPI1/**","/securetAPI2/**","/securetAPI3/**","/securetAPI5/**").access("hasRole('ADMIN')")
.and().httpBasic().authenticationEntryPoint(authEntryPoint);
}
/**
* Build Authentication and attach it to the appContext
*
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
Then my AuthentivationEntryPoint contains this code:
#Component
public class AuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
/**
* Authentication Entry Point on our application which check if the user allowed or not,
* and give response back based on servlet on the case of failure
*
*/
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authEx.getMessage());
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MBO");
super.afterPropertiesSet();
}
}
And of the UserDetailsService class contain this snippet code:
#Service
public class TheUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
/**
*
* retrieve authenticate user by his Role to verify it later if he is autorized to access the API or Not
*
*/
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User retrievedUser = userRepository.findByUserName(username);
List<GrantedAuthority> authorities = this.createUserAuthorities(retrievedUser.getUserRole());
return (UserDetails) this.buildUserForAuthentication(retrievedUser, authorities);
}
private User buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
User usr = new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, authorities);
return usr;
}
/**
* give the user authority based on his Role in database, which gonna be converted by our method to SIMPLE.GRANTED.AUTHORITY
* to be processed later in Servlet intercepting and check authorities
*
* One USER can have multiple authorities for that reason the return back a List<GrantedAuthority>.
* #param userRoles
* #return
*/
private List<GrantedAuthority> createUserAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authsList = new HashSet<GrantedAuthority>();
// create user's authorities
userRoles.forEach(userRole -> {
authsList.add(new SimpleGrantedAuthority(userRole.getRole()));
});
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(authsList);
return Result;
}
}
So till now when even i try to access one of this Rest API "/securetAPI1/**" "/securetAPI2/**" "/securetAPI3/**" "/securetAPI5/**" from browser its show me this spring security session authentication popup
Any one can suggest me correction or any tutorial who to build my Login RestController that submit user-sessionID and bypass the spring security authentication popup for the rest of API accessAttempt?
My RestController Login try:
#RestController
#RequestMapping(value="/")
public class LoginController {
#Autowired
private UserDetailsService userDetail;
#RequestMapping(value="/loginPage", method = RequestMethod.POST)
public void login(#RequestBody UserDto user, HttpServletResponse response) throws IOException{
if(user.getUsername() != null && user.getPassword() != null) {
UserDetails userDetails = userDetail.loadUserByUsername(user.getUsername());
if(userDetails != null) {
response.sendRedirect("/application-context/securetAPI1/");
}
else
System.out.println("Failed");
}
}
}
Related
I am using Spring Security with JWT for authenticating rest apis. On login, the JWT token is generated and shared to the mobile client. But, the token in the subsequent requests are not been validated. Is there anything wrong in the security configuration ?
Spring security version - 5.1.6.RELEASE
// Security Configuration
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JwtTokenAuthenticationFilter jwtTokenAuthenticationFilter;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.
httpBasic().disable().
csrf().disable().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).
and().
addFilterBefore(jwtTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
authorizeRequests().antMatchers("/user/login").permitAll().
antMatchers("/user/test").authenticated().
anyRequest().authenticated();
}
}
// JWT Token Auth Filter - This is never invoked
#Component
public class JwtTokenAuthenticationFilter extends GenericFilterBean {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (null != token && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
if (null != auth) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(req, res);
}
}
I was expecting that all the requests after login will be authenticated against the JWT token.
I tried putting the name of the service to be authenticated as below:
antMatchers("/user/test").authenticated().
Also, any request authenticated is also added, but neither of them worked.
anyRequest().authenticated();
Try to replace
addFilterBefore(jwtTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).
by
addFilterBefore(jwtTokenAuthenticationFilter), BasicAuthenticationFilter.class);
If not runs, change jwtTokenAuthenticationFilter class to avoid to be a Spring bean and use like this:
addFilterBefore(new JwtTokenAuthenticationFilter(jwtTokenProvider)), BasicAuthenticationFilter.class);
And add the following code on Security Class:
#Autowired
private JwtTokenProvider jwtTokenProvider;
I am trying to get the currently logged in user (basic auth) in a spring boot REST backend:
#RequestMapping(value = "/someURL", method = RequestMethod.GET)
public #ResponseBody Map someMethod(
Authentication auth, // <==== works
#AuthenticationPrincipal(expression = "liquidoUserModel") UserModel liquidoUserModel
)
{
log.debug(auth.getPrincipal()) // <==== THIS WORKS
log.debug(liquidoUserModel) // <==== returns an intance with empty fields
}
Here is my custom UserModel
#Data // Lombok magic for all the getters and setters
#Entity
#RequiredArgsConstructor
#Table(name = "users")
public class UserModel {
#Id
Long id;
#NonNull
#Column(unique = true)
public String email;
[...]
}
And this is my UserDetailsService
public class LiquidoUserDetailsService implements UserDetailsService {
#Autowired
UserRepo userRepo;
#Override
public LiquidoAuthUser loadUserByUsername(String email) throws UsernameNotFoundException {
UserModel userModel = userRepo.findByEmail(email);
return new LiquidoAuthUser(userModel.getEmail(), userModel.getPasswordHash(), getGrantedAuthorities(userModel), userModel);
}
}
And finally the LiquidoAuthUser
public class LiquidoAuthUser extends User {
private UserModel liquidoUserModel;
public LiquidoAuthUser(String username, String password, Collection<? extends GrantedAuthority> authorities, UserModel liquidoUserModel) {
super(username, password, authorities);
this.liquidoUserModel = liquidoUserModel;
}
public UserModel getLiquidoUserModel() {
return liquidoUserModel;
}
public void setLiquidoUserModel(UserModel userModel) {
this.liquidoUserModel = userModel;
}
}
And of course I have the #EnableWebMvc annotation on my main SpringApplication class.
My problem: How can I get the currently authenticated custom UserModel in the REST handler?
The strange thing: I actually can get my custom user from the Authentication auth object. Why does the #AuthenticationPrincipal not work?
I am using spring-security-4.1.3-RELEASE
Full code is open source at https://github.com/Doogiemuc/liquido-backend-spring
I tried and debug your code but not able to find issue of
#AuthenticationPrincipal. Typically this annotation is resolved by AuthenticationPrincipalArgumentResolver class of spring security web annotation. By using #EnableWebSecurity you will automatically have this added to your Spring MVC configuration. Need to be debug more on AuthenticationPrincipalArgumentResolver that for time being I will suggest go with Authentication class and get your object.
Using Dropwizard Authentication 0.9.0-SNAPSHOT
I want to check the credentials against database user (UserDAO).
I get the following exception
! org.hibernate.HibernateException: No session currently bound to
execution context
How to bind the session to the Authenticator?
Or are there better ways to check against the database user?
The Authenticator Class
package com.example.helloworld.auth;
import com.example.helloworld.core.User;
import com.example.helloworld.db.UserDAO;
import com.google.common.base.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {
UserDAO userDAO;
public ExampleAuthenticator(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
Optional<User> user;
user = (Optional<User>) this.userDAO.findByEmail(credentials.getUsername());
if ("secret".equals(credentials.getPassword())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
The Application Class
#Override
public void run(HelloWorldConfiguration configuration, Environment environment) throws Exception {
final UserDAO userDAO = new UserDAO(hibernate.getSessionFactory());
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleAuthenticator(userDAO))
.setAuthorizer(new ExampleAuthorizer())
.setRealm("SUPER SECRET STUFF")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(new UserResource(userDAO));
To get auth to work with 0.9+ you need the following. You can refer to this particular changeset as an example.
Include the dependency.
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
<version>${dropwizard.version}</version>
</dependency>
Register auth related stuff.
private void registerAuthRelated(Environment environment) {
UnauthorizedHandler unauthorizedHandler = new UnAuthorizedResourceHandler();
AuthFilter basicAuthFilter = new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new BasicAuthenticator())
.setAuthorizer(new UserAuthorizer())
.setRealm("shire")
.setUnauthorizedHandler(unauthorizedHandler)
.setPrefix("Basic")
.buildAuthFilter();
environment.jersey().register(new AuthDynamicFeature(basicAuthFilter));
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(unauthorizedHandler);
}
A basic authenticator
public class BasicAuthenticator<C, P> implements Authenticator<BasicCredentials, User> {
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
//do no authentication yet. Let all users through
return Optional.fromNullable(new User(credentials.getUsername(), credentials.getPassword()));
}
}
UnAuthorizedHandler
public class UnAuthorizedResourceHandler implements UnauthorizedHandler {
#Context
private HttpServletRequest request;
#Override
public Response buildResponse(String prefix, String realm) {
Response.Status unauthorized = Response.Status.UNAUTHORIZED;
return Response.status(unauthorized).type(MediaType.APPLICATION_JSON_TYPE).entity("Can't touch this...").build();
}
#Context
public void setRequest(HttpServletRequest request) {
this.request = request;
}
}
Authorizer
public class UserAuthorizer<P> implements Authorizer<User>{
/**
* Decides if access is granted for the given principal in the given role.
*
* #param principal a {#link Principal} object, representing a user
* #param role a user role
* #return {#code true}, if the access is granted, {#code false otherwise}
*/
#Override
public boolean authorize(User principal, String role) {
return true;
}
}
Finally use it in your resource
#GET
public Response hello(#Auth User user){
return Response.ok().entity("You got permission!").build();
}
You're going to need code in your Application class that looks like this
environment.jersey().register(AuthFactory.binder(new BasicAuthFactory<>(
new ExampleAuthenticator(userDAO), "AUTHENTICATION", User.class)));
Then you can use the #Auth tag on a User parameter for a method and any incoming authentication credentials will hit the authenticate method, allowing you to return the correct User object or absent if the credentials are not in your database.
EDIT: Works for Dropwizard v0.8.4
On Latest versions starting from 0.9 onward, you can use "#Context" annotation in resource class methods as shown below:
#RolesAllowed("EMPLOYEE")
#Path("/emp")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getEmployeeResponse(#Context SecurityContext context) {
SimplePrincipal sp = (SimplePrincipal) context.getUserPrincipal();
return Response.ok("{\"Hello\": \"Mr. " + sp.getUsername() + "\"( Valuable emp )}").build();
}
I have two end-points.
/foo - is a internal (semi-private) endpoint. It is allowed only for the clients configured. (No userName and credentials are needed; but clientID is sufficient)
/greetings - is a private endpoint. It is allowed only for clients and user configured. (both clientID and username and password is required)
Here is the configuration.
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources
.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN")
.antMatchers("/greeting").authenticated()
.antMatchers("/foo").authenticated();
// #formatter:on
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// #formatter:off
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password", "refresh_token","authorization_code")
.authorities("USER","ROLE_CLIENT")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret("123456")
.accessTokenValiditySeconds(600);
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
oauthServer.checkTokenAccess("permitAll()");
}
}
}
Here is the controller
#RestController
public class GreetingController {
private static final String template = "Hello, %s! your password is %s";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#AuthenticationPrincipal User user) {
return new Greeting(counter.incrementAndGet(), String.format(template, user.getName(),user.getPassword()));
}
#RequestMapping("/foo")
public String foo(#AuthenticationPrincipal User user) {
System.out.println(user==null);
return "you are permitted here";
}
}
I am NOT able to access http://localhost:9001/foo without any token.
so when I try to get access token using the curl below ( note I do not pass username and password, only client_id and client_secret is passed)
curl -X POST -vu clientapp:123456 http://localhost:9001/oauth/token -H "Accept: application/json" -d "grant_type=password&scope=read%20write&client_secret=123456&client_id=clientapp"
I get this error
{"error":"invalid_grant","error_description":"Bad credentials"}
something is wrong with my configuration. I am a beginner using Spring security OAuth. would appreciate any help here.
thanks
I think you are passing wrong grant_type...
curl -X POST -vu clientapp:123456 http://localhost:9001/oauth/token -H "Accept: application/json" -d "grant_type=client_credentials&scope=read%20write&client_secret=123456&client_id=clientapp"
Also as far as I remember you shouldn't duplicate client_id and client_secret in the header and body of request.. Only header should be enough
I'm having some trouble to get Spring MVC & Spring Security working with Java Config. I'm using an initializer to perform the initialization:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* Profile the app runs in.
*/
private final String activeProfile = EnvironmentProfiles.DEV;
/**
*
* #return
*/
#Override
protected Class<?>[] getRootConfigClasses() {
if (this.activeProfile.equals(EnvironmentProfiles.PRODUCTION)) {
return new Class[]{ProductionMainConfig.class};
} else {
return new Class[]{DevMainConfig.class};
}
}
/**
*
* #return
*/
#Override
protected Class<?>[] getServletConfigClasses() {
if (this.activeProfile.equals(EnvironmentProfiles.PRODUCTION)) {
return new Class[]{ProductionWebMvcConfig.class};
} else {
return new Class[]{DevWebMvcConfig.class};
}
}
/**
*
* #return
*/
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.setInitParameter("spring.active.profile", this.activeProfile);
}
In AppInitializer, activeProfile is just a way to set the active profile through a context init parameter.
ProductionWebMvcConfig & DevWebMvcConfig classes are the ones extending WebMvcConfigurerAdapter where view resolvers are configured and such.
My DevMainConfig & ProductionMainConfig classes are like RootConfiguration classes that includes other configurations classes.
#Configuration
#Profile(EnvironmentProfiles.DEV)
#Import(value = {
DevDataSourceConfig.class,
DevInfrastructureConfig.class,
DevRepositoryConfig.class,
DevServiceConfig.class,
DevSecurityConfig.class,
DevWebMvcConfig.class
})
#ComponentScan("org.rz.app.conf.spring")
public class DevMainConfig {
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DevMainConfig.class);
/**
* Application environment.
*/
#Autowired
private Environment env;
/**
* {#inheritDoc}
*/
#PostConstruct
public void initApp() {
if(LOG.isInfoEnabled())
for (String profile : env.getActiveProfiles()) {
LOG.info("Detected Spring profile: {}", profile);
}
}
}
Also I have an AppSecurityInitializer class that extends AbstractSecurityWebApplicationInitializer. This class is supossed to set up Spring Security "springSecurityFilterChain" so no further configuration is required.
The configuration is properly detected by Tomcat because it's printing the following message to log.
Spring WebApplicationInitializers detected on classpath: [org.rz.app.conf.spring.AppSecurityInitializer#71d07e60, org.rz.app.conf.spring.AppInitializer#5a757797]
I have no web.xml or any other xml configuration file, but the exception
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined
is still present.
Any ideas?
Thanks.
Ok. It's solved.
Redeploying the app in another Tomcat 7 (same version) solved the issue.