I am trying to secure Rest APIs using spring boot and JWT. Right now I have been able to piece together pieces of the configuration to get a token generated with a hard coded username and password. I would like my User class and repository to be used instead.
I have been able to hardcode a user here
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder().encode("password"))
.authorities("ROLE_USER");
}
Should I be pointing this to my UserDetailsService? How would I do that?
#Service
public class UserSecurityService implements UserDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(UserSecurityService.class);
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (null == user) {
LOG.warn("username not found");
throw new UsernameNotFoundException("Username" + username + "not found");
}
return user;
}
}
For UserDetailsService, you need DaoAuthenticationProvider to handle any authentication requests.
To do so:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
// you shouldn't use plain text
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
above internally configures a DaoAuthenticationProvider. Alternatively, you can define a bean to inject:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
Related
I was developing a backend with spring boot for the first time. I chose email password login over username password login which is the default behavior of spring boot.
I have two objects: AppUser and User. AppUser is used for authentication and User holds every other data necessary for the application.
AppUser:
public class AppUser implements UserDetails {
#Autowired
AuthRepository repository;
#Id
private String id;
#NonNull
#Indexed(unique = true)
#Field(value = "email")
String email;
#NonNull
#Field(value = "password")
String password;
#Field(value = "authorities")
private List<Role> authorities;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> auth = new HashSet<>();
authorities.forEach(index -> auth.add(new SimpleGrantedAuthority(String.valueOf(index.getRole()))));
return auth;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
AuthController looks like:
#RestController
#RequestMapping("/api/v1/Auth")
public class AuthController {
Logger logger = LoggerFactory.getLogger(AuthController.class);
#Autowired
UserDetailsManager userDetailsManager;
#Autowired
UserService userService;
#Autowired
AuthService authService;
#Autowired
TokenGenerator tokenGenerator;
#Autowired
DaoAuthenticationProvider daoAuthenticationProvider;
#Autowired
#Qualifier("jwtRefreshTokenAuthProvider")
JwtAuthenticationProvider refreshTokenAuthProvider;
#PostMapping("/SignUp")
public ResponseEntity signup(#RequestBody SignUpRequest signUpRequest) {
logger.info(signUpRequest.getUsername());
User user = new User(
signUpRequest.getEmail(),
signUpRequest.getPassword(),
signUpRequest.getUsername(),
signUpRequest.getFirstName(),
signUpRequest.getLastName(),
signUpRequest.getCountry(),
null,
false,
false
);
AppUser appUser = new AppUser(
signUpRequest.getEmail(),
signUpRequest.getPassword()
);
boolean alreadyExists = authService.existsByEmail(signUpRequest.getEmail());
if(alreadyExists){
return ResponseEntity.status(HttpStatus.CONFLICT).body("Email Already Exists");
}else{
userDetailsManager.createUser(appUser);
User newUser = userService.addUser(user);
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(appUser, signUpRequest.getPassword(), Collections.EMPTY_LIST);
return ResponseEntity.ok(tokenGenerator.signup(authentication, newUser));
}
}
#PostMapping("/SignIn")
public ResponseEntity signin(#RequestBody SignInRequest signInRequest) {
logger.info(signInRequest.getEmail());
logger.info(signInRequest.getPassword());
Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(signInRequest.getEmail(), signInRequest.getPassword()));
logger.info(authentication.getCredentials().toString());
User user = new User();
return ResponseEntity.ok(tokenGenerator.signin(authentication, user));
}
}
The issue is with signin. SignUp works fine when I am providing email and password. Login is not working.
I am quite new so have zero idea where the issue is happening. Any reference code with explanation will help a lot.
Github Link
N.B: using spring boot, mongodb, oauth2
In my understanding based on your WebSecurity.class, your are using OAuth2 authentication.
What I would do is create 2 Configurations inside the same class with a different Order and in the second one a custom AuthenticationProvider. This was Spring will try to authenticate first based on the first class and then on the second one
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
//here we set as the first in order the below config if this fails the next one in order will be used until any of them succeeds
#Configuration
#Order(1)
public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private MyLoginRequestFilter myLoginRequestFilter ;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http
.antMatchers("/api/v1/Auth/*").permitAll()
.addFilterBefore(myLoginRequestFilter, UsernamePasswordAuthenticationFilter.class )
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.cors()
.disable()
.csrf()
.disable()
;
}
}
Then insert your class below with Order 2 and then close the whole file/class
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Slf4j
#Order(2)
//this is your class that is uploaded in the git. We actually encapsulate inside a generic Security Config class
public class WebSecurity {
#Autowired
JwtToUserConverter jwtToUserConverter;
...
}
} //to close the java class that was started in the previous block of code
You could even add a new function that will handle the HttpSecurity item so that you dont duplicate your code:
public HttpSecurity myHandler(HttpSecurity http) {
return http
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/api/v1/Auth/*").permitAll()
.antMatchers("/api/v1/Home/*").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.cors().disable()
;
}
Note that in the Order(1) example, I have added a filter, but you can leave it without a filter and add a custom AuthenticationProvider with inside the class:
#Autowired
private YourAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
YourAuthenticationProvider could look like
#Component
public class YourAuthenticationProvider implements AuthenticationProvider {
#Autowired
AuthService authService; //need to move here the authenticate method from the controller Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(signInRequest.getEmail(), signInRequest.getPassword()));
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String email = authentication.getName();
final Object credentials = authentication.getCredentials();
if ( credentials == null ) {
return authentication;
}
//I suggest using a password encoder to save the password and then check it with PasswordEncoder (you could create a bean)
final Authentication auth = authService.authenticate(email, credentials.toString());
if (auth != null) {
return auth;
}
throw new BadCredentialsException( "Wrong username and/or password" );
}
}
Hope these help
I have a spring boot application with a mongo databse and spring security as a dependency.
It has two services first one for authentication and second one for application resource (entities, services controllers).
This is my config class in the authentication service:
#Configuration
#EnableWebSecurity
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
protected UserDetailsService userDetailsService() {
return new MongoUserDetailsService();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().authenticated();
System.out.println("auth");
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Bean(name="authenticationManager")
#Lazy
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
this is the rest controller:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping(value = "/api/users")
public class UserController {
#Autowired
UserServiceImpl userServiceImpl;
//Getting all users
#GetMapping(value = "")
public List<UserDTO> getAllUsers() {
return userServiceImpl.getAllUsers();
}
//Getting a user by ID
#GetMapping(value = "/profil/{userId}")
public UserDTO getUserById(#PathVariable String userId) {
return userServiceImpl.getUserById(userId);
}
//Getting a user by Username
#GetMapping(value = "/profil/username/{username}")
public UserDTO getUserByUsernameOrEmail(String username) {
return userServiceImpl.getUserByUsernameOrEmail(username);
}
//Logout user and delete token
#PostMapping("/logout")
public void logout(HttpServletRequest request) {
userServiceImpl.logout(request);
}
I changed my configure method to this :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests() // authorize
.anyRequest().authenticated() // all requests are authenticated
.and()
.httpBasic();
http.cors();
}
Now i get 401 unauthorized when acceccing protected resources.The problem is now even when i send the correct bearer token in the request header i still get 401 unauthorized "Full authentication is required to access this resource"
Update:
I changed my project architecture from microservices to one simple spring boot project.
this is the new code of the class "AuthServerSecurityConfig"
#Configuration
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
protected UserDetailsService userDetailsService() {
return new MongoUserDetailsService();
}
#Autowired
BCryptPasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll().and()
.httpBasic();
http.cors();
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Bean(name="authenticationManager")
#Lazy
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
and this "ResourceServerConfig" code:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired private ResourceServerTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("foo").tokenServices(tokenServices);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // authorize
.antMatchers("/oauth/**").permitAll();
http
.authorizeRequests().antMatchers("/api/**").authenticated();
http
.headers().addHeaderWriter(new HeaderWriter() {
#Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getMethod().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
}
}
});
}
}
When i try to access protected resource i get "error": "unauthorized",
"error_description": "Full authentication is required to access this resource", Which is the normal behaviour.The problem is now i can't login to get the user aceess_token.
I get "401 unauthorized" when accessing this endpoint "http://localhost:8080/oauth/token?grant_type=password&username=user&password=user".
These are the default init user credentials and the user exists in my mongodatabase with a crypted and correct format password starts with "$2a" and has "60" caracters.
I get in "Encoded password does not look like BCrypt
Authentication failed: password does not match stored value" in the console when trying to login.
In ResourceServerConfig class file, in the configure method change to the below code.
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll().and()
.httpBasic();
Let me know if it worked.
Here's an example of configuration spring security with a jwt token check :
you can change the data source from h2 to mongodb and find the filters and providers used in my repo :
https://github.com/e2rabi/sbs-user-management/tree/main/sbs-user-management
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
#FieldDefaults(level = PRIVATE, makeFinal = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
// Put your public API here
new AntPathRequestMatcher("/public/**"),
new AntPathRequestMatcher("/h2-console/**"),
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
TokenAuthenticationProvider provider;
SecurityConfig(final TokenAuthenticationProvider provider) {
super();
this.provider = requireNonNull(provider);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
// this entry point handles when you request a protected page and you are not yet
// authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.cors().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
// h2 console config
http.headers().frameOptions().sameOrigin();
// disable page caching
http.headers().cacheControl();
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
}
Hope that my answer can help,
you can drop a breakpoint to the line change the response status, and then check who and why it returns 403, it can finally help you get the solution
Drop a breakpoint on the line set the 403 status, to see how this happen from the stackframes.
Guess that it returns 403 without much other information, but it must need to set the status to the response, right? So drop a breakpoint to the setStatus method, I don't know where it should locate, in tomcat lib, spring lib, or servlet lib. Check the HttpResponse, they're several implementation, set the breakpoints for those setStatus/setCode methods. (Next you can see it acutally happens at HttpResponseWrapper::setStatus)
Analyze the stackframes to see what's going on there
please check https://stackoverflow.com/a/73577697/4033979
I am following Spring in action book and creating a small demo application to enhance my spring knowledge.
When i tried to setup spring security with PostgreSQL and tried to test authenticated requests, I am always getting 403 forbidden error with no error messages in console log.
I would like to understand whats wrong with the code.
I tried to add debug level for security, introduced AuthenticationEventListener to monitor the events etc. But none of them tell me why authentication fails.
Register controller working fine and its saving user details to DB with encoded password.
For complete code please look into https://github.com/vin0010/Spring-Recipe
SecurityConfig.java
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
private UserRepository userRepository;
//TODO check why it didn't work
#Autowired
#Qualifier("name")
private UserDetailsService userDetailsService;
#Value("${spring.queries.users-query}")
private String usersQuery;
#Value("${spring.queries.roles-query}")
private String rolesQuery;
#Autowired
public BCryptPasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/test").authenticated()
.antMatchers("/register", "/**").permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.and()
.jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(passwordEncoder);
}
}
TestController for authentication check
#RestController
#RequestMapping("test")
public class TestController {
#GetMapping
public String getDocument(){
return "success";
}
}
Registration controller
#RequestMapping("/register")
public class RegistrationController {
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.ACCEPTED)
public void send(#RequestBody RegistrationForm registrationForm) {
userRepository.save(registrationForm.toUser(passwordEncoder));
}
}
UserRepository.java
public interface UserRepository extends CrudRepository<Users, Long> {
Users findByUsername(String username);
}
UserRepositoryUserDetailsService.java
#Service("name")
public class UserRepositoryUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
Users users= userRepository.findByUsername(userName);
if (users == null)
throw new UsernameNotFoundException("User '" + userName + "' not found");
return users;
}
}
Users.java
#Entity
#Data
#NoArgsConstructor(access= AccessLevel.PRIVATE, force=true)
#RequiredArgsConstructor
public class Users implements UserDetails {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private final String username;
private final String password;
private final boolean enabled;
private final String role;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority(this.role));
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return this.enabled;
}
}
queries for user authentication and authorities check
spring.queries.users-query=select username, password, enabled from users where username=?
spring.queries.roles-query=select username, role from users where username=?
Since I created my own User table, I need to get the authentication done via this en entity setup.
I am relatively new to the Spring boot frame work. I had a basic web application built in Angular with Spring boot connected and to a Mongodb. The application allowed users to add todo lists and register for the the website. When the application started it returned the todolists stored in mongodb to the view. The user could register, and there details were stored in a Mongo repository.
When I added and implemented spring security I got the error message
Circular view path [login]: would dispatch back to the current handler URL [/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
What I want to happen is, when the webapp loads, I want the index.html to be injected with todo.html. Then if a user logs in they will be directed to another page or some Ui feature to become available. At the moment I am stuck in this Circular view pathloop.
I have looked through the different answers but I still am confused as to what exactly is causing the issue. I believe it is in the WebSecurityConfig class
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
UserDetailsService userDS;
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers("/api/todos/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDS);
}
#Override
protected UserDetailsService userDetailsService() {
return userDS;
}
}
AuthUserDetailsService
#Repository
public class AuthUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository users;
private org.springframework.security.core.userdetails.User userdetails;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
todoapp.models.User user = getUserDetail(username);
userdetails = new User (user.getUsername(),
user.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(user.getRole())
);
return userdetails;
}
public List<GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
if (role.intValue() == 1) {
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
} else if (role.intValue() == 2) {
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
}
return authList;
}
private todoapp.models.User getUserDetail(String username){
todoapp.models.User user = users.findByUsername(username);
return user;
}
}
TodoController
#RestController
#RequestMapping("/api/todos")
public class TodoController {
#Autowired
TodoRepository todoRepository;
#RequestMapping(method=RequestMethod.GET)
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
#RequestMapping(method=RequestMethod.POST)
public Todo createTodo(#Valid #RequestBody Todo todo) {
return todoRepository.save(todo);
}
#RequestMapping(value="{id}", method=RequestMethod.GET)
public ResponseEntity<Todo> getTodoById(#PathVariable("id") String id) {
Todo todo = todoRepository.findOne(id);
if(todo == null) {
return new ResponseEntity<Todo>(HttpStatus.NOT_FOUND);
} else {
return new ResponseEntity<Todo>(todo, HttpStatus.OK);
}
}
#RequestMapping(value="{id}", method=RequestMethod.PUT)
public ResponseEntity<Todo> updateTodo(#Valid #RequestBody Todo todo, #PathVariable("id") String id) {
Todo todoData = todoRepository.findOne(id);
if(todoData == null) {
return new ResponseEntity<Todo>(HttpStatus.NOT_FOUND);
}
todoData.setTitle(todo.getTitle());
todoData.setCompleted(todo.getCompleted());
Todo updatedTodo = todoRepository.save(todoData);
return new ResponseEntity<Todo>(updatedTodo, HttpStatus.OK);
}
#RequestMapping(value="{id}", method=RequestMethod.DELETE)
public void deleteTodo(#PathVariable("id") String id) {
todoRepository.delete(id);
}
}
RecourceController
#Configuration
public class ResourceController extends WebMvcConfigurerAdapter{
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/api/todos").setViewName("home");
registry.addViewController("/register").setViewName("register");
registry.addViewController("/login").setViewName("login");
}
}
Any help will be greatly appreciated.
This is the Project Layout.
You forgot to add .html to your view names:
registry.addViewController("/").setViewName("app/views/index.html");
registry.addViewController("/api/todos").setViewName("app/views/home.html");
registry.addViewController("/register").setViewName("app/views/register.html");
registry.addViewController("/login").setViewName("app/views/login.html");
Spring Boot registers a ResourceHttpRequestHandler which is capable of resolving static resources under static folder.
Because you set login as view name ResourceHttpRequestHandler tries to load static/login which apparently does not exist.
Change it to app/views/login.html so that static/login becomes static/app/views/login.html.
Could anyone please help me with an example of a Spring Boot application that contains a Rest Service with endpoints protected by Spring Security using oAuth2 with user credentials from a MySQL database?
How about this one: https://github.com/spring-projects/spring-security-oauth/tree/master/tests/annotation/jdbc (it's not MySQL, but it's JDBC, so the transformation is trivial)?
Please refer to https://github.com/royclarkson/spring-rest-service-oauth/
and perform following changes, It uses primary datasource defined in application.properties,
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "rest_api";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN")
.antMatchers("/review").authenticated()
.antMatchers("/logreview").authenticated()
.antMatchers("/oauth/token").authenticated()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
;
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(new JdbcTokenStore(dataSource))
.authenticationManager(this.authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setAccessTokenValiditySeconds(300);
tokenServices.setRefreshTokenValiditySeconds(6000);
tokenServices.setTokenStore(new JdbcTokenStore(dataSource));
return tokenServices;
}
}
}