I have been struggling on this issue for 2 days. I have a simple Springboot application with spring security. When I tested my controller using Swagger and Postman, there is no issue. However, when I call the same endpoint from my front-end app, it throws the error below
2022-10-07T21:43:51.991+0800 DEBUG http-nio-8080-exec-1 (FilterChainProxy.java:323) - Secured OPTIONS /category/all 2022-10-07T21:43:51.993+0800 DEBUG http-nio-8080-exec-1 (LogFormatUtils.java:119) - OPTIONS "/category/all", parameters={} 2022-10-07T21:43:51.995+0800 DEBUG http-nio-8080-exec-1 (PropertySourcedRequestMappingHandlerMapping.java:108) - looking up handler for path: /category/all 2022-10-07T21:43:51.998+0800 DEBUG http-nio-8080-exec-1 (AbstractHandlerMapping.java:522) - Mapped to com.edar.sales.be.controller.CategoryController#getAllCategories() 2022-10-07T21:43:52.002+0800 DEBUG http-nio-8080-exec-1 (OpenEntityManagerInViewInterceptor.java:86) - Opening JPA EntityManager in OpenEntityManagerInViewInterceptor 2022-10-07T21:43:52.015+0800 DEBUG http-nio-8080-exec-1 (HttpSessionSecurityContextRepository.java:346) - Did not store anonymous SecurityContext 2022-10-07T21:43:52.018+0800 DEBUG http-nio-8080-exec-1 (OpenEntityManagerInViewInterceptor.java:111) - Closing JPA EntityManager in OpenEntityManagerInViewInterceptor 2022-10-07T21:43:52.019+0800 DEBUG http-nio-8080-exec-1 (FrameworkServlet.java:1131) - Completed 403 FORBIDDEN
This is my Controller Class
package com.edar.sales.be.controller;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.edar.sales.be.dto.CategoryDTO;
import com.edar.sales.be.entity.Category;
import com.edar.sales.be.service.CategoryService;
import com.google.gson.Gson;
#RestController(value = "category/")
public class CategoryController {
private static final Logger LOG = LoggerFactory.getLogger(CategoryController.class);
private static final Gson GSON = new Gson();
#Autowired
CategoryService categoryService;
#GetMapping(value = "category/all")
public List<CategoryDTO> getAllCategories() throws IllegalAccessException, InvocationTargetException {
List<CategoryDTO> retval = new ArrayList<>();
List<Category> categories = categoryService.getAllCategories();
for (Category category : categories) {
CategoryDTO categoryDTO = new CategoryDTO();
BeanUtils.copyProperties(categoryDTO, category);
retval.add(categoryDTO);
}
return retval;
}
#GetMapping(value = "category/{id}")
public CategoryDTO getCategoryById(#PathVariable("id") long id) throws IllegalAccessException, InvocationTargetException {
CategoryDTO categoryDTO = new CategoryDTO();
BeanUtils.copyProperties(categoryDTO, categoryService.getCategoryById(id));
return categoryDTO;
}
#PostMapping(value = "category/delete/{id}")
public void deleteCategoryById(#PathVariable("id") Long id) {
categoryService.deleteCategoryById(id);
}
#PostMapping(value = "category/add")
public void addCategory(#RequestBody Category category) {
LOG.debug("Adding category : {}", GSON.toJson(category));
categoryService.addCategory(category);
}
#PatchMapping(value = "category/update")
public void updateCategory(#RequestBody Category category) {
LOG.debug("Updating category : {}", GSON.toJson(category));
categoryService.addCategory(category);
}
}
And this is my SecurityConfig
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("**").permitAll();
}
}
Try using this and also use SecurityFilterChain for spring security because WebSecurityConfigurerAdapter is deprecated.
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().antMatchers("/**").permitAll()
.and
.httpBasic();
}
}
Related
I am configuring spring security in my project using jwt token.
I am sucessfully generated jwt token and accessing it from front end.
In my spring boot REST APT I have several controllers with all CRUD methods.
I want to give access of get method to all the users and even to public, while
for POST,PUT and Delete I want to give access to only admin and moderator depending on the case.
But for some POST method like in inquiry form i want to give access to all users.
What approach should I follow for that,
Do i need to write
#PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
for each method in each controller.
Right now i just build a test page to check access of roles .
package com.panchmeru_studio.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/test")
public class TestController {
#GetMapping("/all")
public String allAccess() {
return "Public Content.";
}
#GetMapping("/user")
#PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
public String userAccess() {
return "User Content.";
}
#GetMapping("/mod")
#PreAuthorize("hasRole('MODERATOR')")
public String moderatorAccess() {
return "Moderator Board.";
}
#GetMapping("/admin")
#PreAuthorize("hasRole('ADMIN')")
public String adminAccess() {
return "Admin Board.";
}
}
Securityconfig.java
package com.panchmeru_studio.security.jwt;
import com.panchmeru_studio.filter.AuthTokenFilter;
import com.panchmeru_studio.security.service.ApplicationUserDetailsService;
import com.panchmeru_studio.security.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import static com.panchmeru_studio.constants.SecurityConstants.SIGN_UP_URL;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private MyUserDetailsService userDetailsService;
// private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
public SecurityConfiguration(MyUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
// this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated().and()
// .addFilter(new AuthenticationFilter(authenticationManager()))
// .addFilter(new AuthorizationFilter(authenticationManager()))
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
// .authorizeRequests().antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
// .anyRequest().authenticated()
// .and()
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Now for example i have 10 controllers, Project ,AboutUS , ProjectCategory,Gallery
Each has different url(Request mapping) so, Do i need to assign #PreAuthorize to each method to each controller and then give request mapping of each controller to security config to give authrization?
If you want to base the access to methods only on user roles, then the approach you described is the correct one.
Since you're using JWTs to authorize access to your APIs, you could secure the APIs based on claims in JWTs instead of the user profile. In such a setup you don't need access to user accounts at all, you make all the decisions based on what is in the JWT. Have a look at this example to see how to set this up in a Spring API.
i am tryig to post an object to the database using postman , the token generated corectlly but the principal is null !? why
in the controller I have the following code .
and the error as shown
the principal tostring is : UsernamePasswordAuthenticationToken [Principal=com.accessjobs.pjp.domain.User#1a1e348b, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[]]
: the principal getName is : null
#RestController
#RequestMapping("/api/job")
#CrossOrigin //to hit the backend server when connect from the frontend server
public class JobController {
Logger logger = LoggerFactory.getLogger(JobController.class);
#Autowired
private JobService jobService;
#Autowired
private MapValidationErrorsService mapValidationErrorsService;
//#valid annotation to validate the object attributes and , i used the following code FieldErrors to display the list of errors from validation
#PostMapping("")
#CrossOrigin
public ResponseEntity<?> add(#Valid #RequestBody Job job, BindingResult result, Principal principal) {
ResponseEntity<?> errorMap = mapValidationErrorsService.MapValidationService(result);
if (errorMap != null) return errorMap;
logger.info("the principal tostring is : "+principal.toString());
logger.info("the principal getName is : "+principal.getName());
Job tempJob = jobService.add(job, principal.getName());
return new ResponseEntity<Job>(tempJob, HttpStatus.CREATED);
}
and the service job code as shown , always give me a null user and catch the error .
that because the email is null.
what is the solution>?
import com.accessjobs.pjp.domain.Job;
import com.accessjobs.pjp.domain.User;
import com.accessjobs.pjp.exceptions.JobIdException;
import com.accessjobs.pjp.repositories.JobRepository;
import com.accessjobs.pjp.repositories.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Locale;
#Service
public class JobService {
Logger logger = LoggerFactory.getLogger(JobService.class);
#Autowired
private JobRepository jobRepository;
#Autowired
private UserRepository userRepository;
public Job add(Job job,String email){
//logic , validation and handling
try {
logger.info("email .is"+email);
User user = userRepository.findByEmail(email);
logger.info("user is ::"+user.toString());
job.setUser(user);
logger.warn(user.getEmail());
job.setUserRole(user.getRole());
logger.info(user.getRole());
//convert the identifier to upper case
job.setJobIdentifier(job.getJobIdentifier().toUpperCase(Locale.ROOT));
return jobRepository.save(job);
}catch (Exception e){
throw new JobIdException("Job Identifier '"+job.getJobIdentifier().toUpperCase(Locale.ROOT)+"' already exists");
}
}
package com.accessjobs.pjp.services;
import com.accessjobs.pjp.domain.User;
import com.accessjobs.pjp.exceptions.EmailAlreadyExistsException;
import com.accessjobs.pjp.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired //define the bean in the main spring boot app
private BCryptPasswordEncoder bCryptPasswordEncoder;//store non readable password in the database
public User register(User newUser){
try{
newUser.setPassword(bCryptPasswordEncoder.encode(newUser.getPassword()));
//user name has to be unique (exception)
newUser.setEmail(newUser.getEmail());
//make sure that pass and confpass are matches
//we dont presist or show the confirm password
newUser.setConfirmPassword("");
return userRepository.save(newUser);
}catch (Exception e){
throw new EmailAlreadyExistsException("Email' "+newUser.getEmail()+"' is already exists");
}
}
}
maybe the error in the database , how can I permit all URLs for the MySQL database
this is the class that implements the user detailsservice
package com.accessjobs.pjp.services;
import com.accessjobs.pjp.domain.User;
import com.accessjobs.pjp.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if(user==null) new UsernameNotFoundException("User not found");
return user;
}
#Transactional
public User loadUserById(Long id){
User user = userRepository.getById(id);
if(user==null) new UsernameNotFoundException("User not found");
return user;
}
}
is there any errors in this class ?
especially in the MySQL permissions and antMatchers?
package com.accessjobs.pjp.security;
import com.accessjobs.pjp.services.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import static com.accessjobs.pjp.security.SecurityConstants.*;
//video rep branch 65
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
#Bean(BeanIds.AUTHENTICATION_MANAGER)
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// .and().headers().frameOptions().sameOrigin() //To enable H2 Database
.and()
.authorizeRequests()
.antMatchers(
"/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/api/users/**").permitAll()
.antMatchers(SIGN_UP_URLS).permitAll()
.antMatchers("jdbc:mysql://localhost:3306/").permitAll()**//here ?**
.antMatchers("/api/users/**").permitAll()
.antMatchers("/api/users/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Make sure your token has a sub property. Use this tool to post the contents of your token (minus any secure properties).
https://jwt.io/
I am trying to secure a REST endpoint via the #Secured annotation of Spring Security. My main application (Spring Boot App) with the security config and the rest controller are in different packages and project.
Main app package: com.myapp.api.web
Rest controller packge: com.myapp.api.rest
Mainapp:
#SpringBootApplication
#ComponentScan(basePackages = "com.myapp.api")
#EntityScan("com.myapp.api")
#RestController
public class ApiApplication extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(ApiApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
{
return application.sources(ApiApplication.class);
}
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true,
securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static final String USERS_CONFIG_FILE_NAME = "users.yml";
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.httpBasic()
.and()
.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
InMemoryUserDetailsManager inMemoryUserDetailsManager, PasswordEncoder passwordEncoder) throws Exception
{
auth.userDetailsService(inMemoryUserDetailsManager).passwordEncoder(passwordEncoder);
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException
{
return new InMemoryUserDetailsManager(
PropertiesLoaderUtils.loadAllProperties(USERS_CONFIG_FILE_NAME, getClass().getClassLoader()));
}
}
Rest controller:
#RestController
public class RestController
{
private final RestService service;
#PostMapping("/rest/v1")
#Secured({"ROLE_ADMIN"})
public List<String> getStates(#RequestBody List<String> Ids)
{
...
}
My rest endpoint is working as long as I am not setting securedEnabled = true. After setting it true I am getting a 404 Not Found as respond message. I've already debugged it and found out that the Spring Security somewhen stops in the filter chain and that the request never reaches the controller.
As far as I tested it, as long as the rest controller is in a different project this error will occure. After moving it to the same project it is working as it should.
Is there something missing in my Securityconfig or what could the problem be?
I am able to get values by your code , I have only changed Password Encoder to default and changed inMemoryAuthentication .
I did it as I don't have your file "users.yml" If you can share a sample , we will look in it , But below is my code. I kept all logic in 2 files just to verify.
Configuration Class
package com.myapp.api.web.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true,
securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static final String USERS_CONFIG_FILE_NAME = "users.yml";
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.httpBasic()
.and()
.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
};
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication()
.withUser("user").password("user").roles("USER")
.and().withUser("admin").password("admin").roles("ADMIN");
}
/*
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException
{
return new InMemoryUserDetailsManager(
PropertiesLoaderUtils.loadAllProperties(USERS_CONFIG_FILE_NAME, getClass().getClassLoader()));
}*/
}
Main Class
package com.myapp.api.web.Api;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#ComponentScan(basePackages = "com.myapp.api")
#RestController
#SpringBootApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
#PostMapping("/rest/v1")
#Secured({"ROLE_ADMIN"})
public List<String> getStates(#RequestBody List<String> Ids)
{
return Ids;
// ...
}
}
I have setup a auth server and resource server as mentioned in the below article
http://www.hascode.com/2016/03/setting-up-an-oauth2-authorization-server-and-resource-provider-with-spring-boot/
I downloaded the code and it is working fine. Now the issue is that in the resource provider project there is only one RestController annotated class as shown below
package com.hascode.tutorial;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Scope;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
#EnableResourceServer
public class SampleResourceApplication {
public static void main(String[] args) {
SpringApplication.run(SampleResourceApplication.class, args);
}
#RequestMapping("/")
public String securedCall() {
return "success (id: " + UUID.randomUUID().toString().toUpperCase() + ")";
}
}
Now when I create a different class annotated with #RestController as shown below
#RestController
#RequestMapping("/public")
public class PersonController {
#Autowired
private PersonRepository personRepo;
#RequestMapping(value = "/person", method = RequestMethod.GET)
public ResponseEntity<Collection<Person>> getPeople() {
return new ResponseEntity<>(personRepo.findAll(), HttpStatus.OK);
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<Person> getPerson(#PathVariable long id) {
Person person = personRepo.findOne(id);
if (person != null) {
return new ResponseEntity<>(personRepo.findOne(id), HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
}
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> addPerson(#RequestBody Person person) {
return new ResponseEntity<>(personRepo.save(person), HttpStatus.CREATED);
}
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Void> deletePerson(#PathVariable long id, Principal principal) {
Person currentPerson = personRepo.findByUsername(principal.getName());
if (currentPerson.getId() == id) {
personRepo.delete(id);
return new ResponseEntity<Void>(HttpStatus.OK);
} else {
return new ResponseEntity<Void>(HttpStatus.UNAUTHORIZED);
}
}
#RequestMapping(value = "/{id}/parties", method = RequestMethod.GET)
public ResponseEntity<Collection<Party>> getPersonParties(#PathVariable long id) {
Person person = personRepo.findOne(id);
if (person != null) {
return new ResponseEntity<>(person.getParties(), HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
}
}
}
but when I tried to access the service (http://localhost:9001/resources/public/person) I am getting 404
{
"timestamp": 1508752923085,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/resources/public/person"
}
when I access http://localhost:9001/resources/ I am getting the correct result like
success (id: 27DCEF5E-AF11-4355-88C5-150F804563D0)
Should I register the Contoller anywherer or am I missing any configuration
https://bitbucket.org/hascode/spring-oauth2-example
UPDATE 1
ResourceServerConfiguration.java
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().and()
.authorizeRequests()
.antMatchers("/resources/public/**").permitAll()
.antMatchers("/resources/private/**").authenticated();
}
}
OAuth2SecurityConfiguration.java
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().and()
.authorizeRequests()
.antMatchers("/resources/public/**").permitAll()
.antMatchers("/resources/private/**").authenticated();
}
}
UPDATE 2
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/public/**").permitAll() //Allow register url
.anyRequest().authenticated().and()
.antMatcher("/resources/**").authorizeRequests() //Authenticate all urls with this body /api/home, /api/gallery
.antMatchers("/resources/**").hasRole("ADMIN")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); //This is optional if you want to handle exception
}
Make your new controller PersonController discoverable by Spring Boot either by using #ComponentScan on a configuration class or by moving PersonController to a package in or under your main class annotated with #SpringBootApplication.
Second fix your OAuth2SecurityConfiguration class like so
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll(); //This will permit the (oauth/token) url for getting access token from the oauthserver.
}
}
Now fix your resource server like so
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/v1/register", "/api/v1/publicOne", "/api/v1/publicTwo").permitAll() //Allow urls
.anyRequest().authenticated().and()
.antMatcher("/api/**").authorizeRequests() //Authenticate all urls with this body /api/home, /api/gallery
.antMatchers("/api/**").hasRole("ADMIN")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); //This is optional if you want to handle exception
}
}
Find the complete source code here. Hope this helps.
Note: You can customize your urls based on the above answer.
Why your request url is http://localhost:9001/resources/public/person
I think it should be like http://localhost:9001/public/person
I am using latest version of Spring Boot and I am trying to setup StatelessAuthenticaion. So far the tutorials I've been reading are very vague and I am not sure what I am doing wrong. The tutorials I am using is...
http://technicalrex.com/2015/02/20/stateless-authentication-with-spring-security-and-jwt/
The problem with my setup is that it seems everything is running correctly except for the fact that TokenAuthenticationService::addAuthentication is never called so my token is never set and therefore it returns null when the TokenAuthenticationService::getAuthentication is called and therefore returns a 401 even when I successfully logged in (Because addAuthentication is never called to set the token in the header). I am trying to figure a way to add TokenAuthenticationService::addAuthentication but I find it quite difficult.
In the tutorial he adds something similar to WebSecurityConfig::UserDetailsService.userService into auth.userDetailsService().. The only problem I am getting with that is when I do so, it throws a CastingErrorException. It only works when I utilize UserDetailsService customUserDetailsService instead...
WebSecurityConfig
package app.config;
import app.repo.User.CustomUserDetailsService;
import app.security.RESTAuthenticationEntryPoint;
import app.security.RESTAuthenticationFailureHandler;
import app.security.RESTAuthenticationSuccessHandler;
import app.security.TokenAuthenticationService;
import app.security.filters.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static PasswordEncoder encoder;
private final TokenAuthenticationService tokenAuthenticationService;
private final CustomUserDetailsService userService;
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
public WebSecurityConfig() {
this.userService = new CustomUserDetailsService();
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
}
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated();
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().defaultSuccessUrl("/").successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
//This is ho
http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
#Bean
#Override
public CustomUserDetailsService userDetailsService() {
return userService;
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
The TokenAuthenticationService successfully calls the getAuthentication method but in the tutorials I read, there is no proper explanation on how addAuthentication is called
TokenAuthenticationService
package app.security;
import app.repo.User.CustomUserDetailsService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TokenAuthenticationService {
private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN";
private final TokenHandler tokenHandler;
//This is called in my WebSecurityConfig() constructor
public TokenAuthenticationService(String secret, CustomUserDetailsService userService) {
tokenHandler = new TokenHandler(secret, userService);
}
public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {
final UserDetails user = authentication.getDetails();
response.addHeader(AUTH_HEADER_NAME, tokenHandler.createTokenForUser(user));
}
public Authentication getAuthentication(HttpServletRequest request) {
final String token = request.getHeader(AUTH_HEADER_NAME);
if (token != null) {
final UserDetails user = tokenHandler.parseUserFromToken(token);
if (user != null) {
return new UserAuthentication(user);
}
}
return null;
}
}
TokenHandler
package app.security;
import app.repo.User.CustomUserDetailsService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
public final class TokenHandler {
private final String secret;
private final CustomUserDetailsService userService;
public TokenHandler(String secret, CustomUserDetailsService userService) {
this.secret = secret;
this.userService = userService;
}
public UserDetails parseUserFromToken(String token) {
String username = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
return userService.loadUserByUsername(username);
}
public String createTokenForUser(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}
In my WebServiceConfig. I add the following
http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
Which calls on the following class as a filter. It gets the Authentication, but there is No where where it actually adds it.
StatelessAuthenticationFilter
package app.security.filters;
import app.security.TokenAuthenticationService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by anthonygordon on 11/17/15.
*/
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService authenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
Authentication authentication = authenticationService.getAuthentication(httpRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.getContext().setAuthentication(null);
}
}
The following class is what gets passed in the TokenAuthenticationService::addAuthentication
UserAuthentication
package app.security;
import app.repo.User.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class UserAuthentication implements Authentication {
private final UserDetails user;
private boolean authenticated = true;
public UserAuthentication(UserDetails user) {
this.user = user;
}
#Override
public String getName() {
return user.getUsername();
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
#Override
public Object getCredentials() {
return user.getPassword();
}
#Override
public UserDetails getDetails() {
return user;
}
#Override
public Object getPrincipal() {
return user.getUsername();
}
#Override
public boolean isAuthenticated() {
return authenticated;
}
#Override
public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated;
}
}
Thats it...
My Solution (But Need Help)...
My solution was to set the TokenAuthenticationService::addAuthentication method in my success handler... The only problem with that is the tutorial added the class TokenAuthenticationService to the WebServiceConfig class. And thats the only place its accessible. If there is a way I can obtain it in my successHandler, I might be able to set the token.
package app.security;
import app.controllers.Requests.TriviaResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by anthonygordon on 11/12/15.
*/
#Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
TriviaResponse tresponse = new TriviaResponse();
tresponse.setMessage("You have successfully logged in");
String json = ow.writeValueAsString(tresponse);
response.getWriter().write(json);
clearAuthenticationAttributes(request);
}
}
You have to call TokenAuthenticationService.addAuthentication() yourself when a user supplies their login credentials the first time.
The tutorial calls addAuthentication() in GoogleAuthorizationResponseServlet after a user successfully logs in using their Google account. Here's the relevant code:
private String establishUserAndLogin(HttpServletResponse response, String email) {
// Find user, create if necessary
User user;
try {
user = userService.loadUserByUsername(email);
} catch (UsernameNotFoundException e) {
user = new User(email, UUID.randomUUID().toString(), Sets.<GrantedAuthority>newHashSet());
userService.addUser(user);
}
// Login that user
UserAuthentication authentication = new UserAuthentication(user);
return tokenAuthenticationService.addAuthentication(response, authentication);
}
If you already have an authentication success handler, then I think you're on the right track that you need to call TokenAuthenticationService.addAuthentication() from there. Inject the tokenAuthenticationService bean into your handler and then start using it. If your success handler doesn't end up being a Spring bean then you can explicitly look tokenAuthenticationService up by calling WebApplicationContextUtils.getRequiredWebApplicationContext.getBean(TokenAuthenticationService.class).
There is also an issue in the tutorial's GitHub repo that will address the confusion between the initial login supplied by the user and the stateless authentication happening on all subsequent requests.
you can define a StatelessLoginFilter like below
.addFilterBefore(
new StatelessLoginFilter("/api/signin",
tokenAuthenticationService, userDetailsService,
authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
and write the class like this
class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
private final TokenAuthenticationService tokenAuthenticationService;
private final UserDetailsService userDetailsService;
protected StatelessLoginFilter(String urlMapping,
TokenAuthenticationService tokenAuthenticationService,
UserDetailsService userDetailsService,
AuthenticationManager authManager) {
super(new AntPathRequestMatcher(urlMapping));
this.userDetailsService = userDetailsService;
this.tokenAuthenticationService = tokenAuthenticationService;
setAuthenticationManager(authManager);
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
Authentication authentication) throws IOException, ServletException {
final User authenticatedUser = userDetailsService
.loadUserByUsername(authentication.getName());
final UserAuthentication userAuthentication = new UserAuthentication(
authenticatedUser);
tokenAuthenticationService.addAuthentication(response,
userAuthentication);
SecurityContextHolder.getContext()
.setAuthentication(userAuthentication);
}
}