Spring Boot OAuth2 with Github - spring-boot

I am implementing OAuth2 architecture with GitHub as authorization server to log into my dummy web application. Everything works perfectly fine. I got problem when I log in using my GitHub credentials I want the redirected page to show some message for user for example Welcome XYZ, but I could not get username who is logged in currently but a number is shown instead. I used principal object as well as Authentication object. Please would any body tell how do I achieve it?
Here is code for OAuth2 GitHub Configuration.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
#Configuration
public class WebSecurityConfigurerAdapterImp extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login((c)->
{
c.clientRegistrationRepository(this.clientRegistrationRepository());
}
);
http.authorizeRequests().anyRequest().authenticated();
}
private ClientRegistrationRepository clientRegistrationRepository()
{
ClientRegistration c=this.clientRegistration();
return new InMemoryClientRegistrationRepository(c);
}
private ClientRegistration clientRegistration()
{
return CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("72bc31d8b0304575442c").clientSecret("XYZSECRET").build();
}
}
Code for main_controller to which user will be redirected after logging in.
package com.controllers;
import java.security.Principal;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class Main_Controller {
#GetMapping("/")
#ResponseBody
public String HomePage(Authentication p)
{
System.out.println("Hellow "+p.getName());
return "Hello People.";
}
}

By default if you use "CommonOAuth2Provider.GITHUB" then principal name is map to "id" of your github user. So, you need to map principal name attribute to "login"

Related

I'm using Cognito + Spring security. There are any way to use authorization?

I'm using spring Security and cognito for authentication and authorization. I entered some custom roles via aws IAM and I would like to know if there was a method to grant controlled access to resources. On the web I found some that set the cognito:groups as a role and used that, but they use deprecated classes and methods on it. Is there any way to do this with the latest versions?
I tried to create a class:
package com.projectname.name.Configurations;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CognitoAccessTokenConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
public CognitoAccessTokenConverter() {
}
#Override
public AbstractAuthenticationToken convert(#NonNull final Jwt jwt) {
Collection<GrantedAuthority> authorities = Stream
.concat(defaultGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream())
.collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities);
}
private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt) {
Collection<String> userRoles = jwt.getClaimAsStringList("cognito:groups");
//System.out.println("\n!!!!!!!!" +userRoles +"!!!!!!!!!!\n"); DEBUG
if (userRoles != null)
return userRoles
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
return Collections.emptySet();
}
}
/*
import java.util.Map;
import java.util.Set;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.
#Component
public class CognitoAccessTokenConverter extends OAuth2AuthorizationCodeRequestAuthenticationToken{
private static final String COGNITO_GROUPS = "cognito:groups";
private static final String SPRING_AUTHORITIES = "authorities";
private static final String COGNITO_USERNAME = "username";
private static final String SPRING_USER_NAME = "user_name";
}
#Component
public class CognitoAccessTokenConverter extends {
// Note: This the core part.
private static final String COGNITO_GROUPS = "cognito:groups";
private static final String SPRING_AUTHORITIES = "authorities";
private static final String COGNITO_USERNAME = "username";
private static final String SPRING_USER_NAME = "user_name";
#SuppressWarnings("unchecked")
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
if (claims.containsKey(COGNITO_GROUPS))
((Map<String, Object>) claims).put(SPRING_AUTHORITIES, claims.get(COGNITO_GROUPS));
if (claims.containsKey(COGNITO_USERNAME))
((Map<String, Object>) claims).put(SPRING_USER_NAME, claims.get(COGNITO_USERNAME));
return super.extractAuthentication(claims);
}
} */
how can I use this conversion in my spring security configuration?
package com.SSDProject.Booked.Configurations;
import java.io.*;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin").hasAuthority("max")
.requestMatchers("/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login();
return http.build();
}
Help me, I tried to implements it and search everywhere. Some helps? Have you an idea?
I've recently created the same PoC using SpringBoot 2.x and Java 17.
In my case I don't have any deprecation warning from my IDE, here my example:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.and()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.userDetailsService(null)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor())));
return http.build();
}
private JwtAuthenticationConverter grantedAuthoritiesExtractor() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
String[] scopes;
if (jwt.getClaims().containsKey("cognito:groups")) {
scopes = ((JSONArray) jwt.getClaims().get("cognito:groups")).toArray(new String[0]);
} else {
scopes = ((String) jwt.getClaims().getOrDefault("scope", "")).split(" ");
}
return Arrays.stream(scopes)
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase(Locale.ROOT)))
.collect(Collectors.toSet());
}
);
return jwtAuthenticationConverter;
}
Exactly which line is deprecated in your code? And what version of resource-server are you using? For me spring-boot-starter-oauth2-resource-server is 2.7.5.
This is actually not an answer but I don't have the reputation for add comment to the question :)
Is your Spring application serving server-side rendered UI (Thymeleaf, JSF or alike) or is it a REST API (#RestController or #Controller with #ResponseBody)?
In second case, your app is a resource-server. OAuth2 login should be handled by clients, not resource-server: clients acquire access token and send it as Authorization header to resource-server.
In my answer to Use Keycloak Spring Adapter with Spring Boot 3, I explain how to configure both Spring resource-servers and clients. All you'll have to adapt for Cognito are issuer URI and the private-claims name to extract authorities from.
Configuring a resource-server with authorities mapped from cognito:groups using my starters (thin wrappers around spring-boot-starter-oauth2-resource-server) can be as simple as:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.10</version>
</dependency>
#Configuration
#EnableMethodSecurity
public class SecurityConfig {
}
com.c4-soft.springaddons.security.issuers[0].location=https://cognito-idp.Region.amazonaws.com/your user pool ID/.well-known/openid-configuration
com.c4-soft.springaddons.security.issuers[0].authorities.claims=cognito:groups
# This is probably too permissive but can be fine tuned (origins, headers and methods can be defined per path)
com.c4-soft.springaddons.security.cors[0].path=/**
If your application is only a client, my starters won't be of any help.
If your app is both a resource-server and a client (serves JSON payloads and server-side rendered UI with, for instance, Thymeleaf), then you'll have to define a second SecurityFilterChain bean. Details in the answer linked earlier.
If you don't want to use my starters, then you'll have to write quite some java conf. Details in the previously linked answer, again.

How to use Spring Security with load balancer?

I am new at loadBalancing so please I need help and thats what i did :
i built 2 services as 2 apps (A,B) I used spring security on both of them
(both of them are restfull api , they have theymleaf and full frontEnd pages ),
then i had made another app as spring cloud loadbalancer .
when i send a request , it go from loadbalancer app to one of the 2 services but the problem is when iam not authenticated the response will be empty , it wont take me to the default login page as usual as when i use the normal A app directly , and when i go to pages that does not need to be authenticated to get to it , it is returned without my css/js styles
this is my A app controller ( it is returning view not json )
package com.hariri_stocks.controllers;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.hariri_stocks.models.Estates;
import com.hariri_stocks.models.SoldEstates;
import com.hariri_stocks.models.Users;
import com.hariri_stocks.services.estatesService;
#Controller
public class LoginController {
#Autowired
estatesService ES;
#GetMapping(value = "/")
public String login() {
return "/signIn-up.html";
}
#GetMapping(value = "/dashboard")
public String dashboard(Model model ,#RequestParam(required = false) String add_result
,#RequestParam(required = false) String alert_err) {
List<Estates> estates = ES.findAll();
model.addAttribute("estates",estates);
return "/dashboard";
}
#GetMapping(value = "/dashboard/unSold")
public String unselled_stocks(Model model) {
List<Estates> estates = ES.findUnsold();
if(estates.size() > 0)
model.addAttribute("estates",estates);
else
model.addAttribute("error","there is no sold estates yet !!");
return "/dashboard";
}
#Value(value = "${server.port}")
String port_num;
#GetMapping("/port")
public String hello() {
return port_num;
}
}
and this is my loadbalancer controller iam using #restcontroller
package com.hariri_loadbalancer;
import reactor.core.publisher.Mono;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
#SpringBootApplication
#RestController
public class UserApplication {
private final WebClient.Builder loadBalancedWebClientBuilder;
private final ReactorLoadBalancerExchangeFilterFunction lbFunction;
public UserApplication(WebClient.Builder webClientBuilder,
ReactorLoadBalancerExchangeFilterFunction lbFunction) {
this.loadBalancedWebClientBuilder = webClientBuilder;
this.lbFunction = lbFunction;
}
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
#RequestMapping("/port")
public Mono<String> showMePort() {
return loadBalancedWebClientBuilder.build().get().uri("http://hariri/port")
.retrieve().bodyToMono(String.class);
}
#RequestMapping("/")
public Mono<String> showMainPage() {
return loadBalancedWebClientBuilder.build().get().uri("http://hariri/")
.retrieve().bodyToMono(String.class);
}
}
So what should I do? I feel that what I am doing is stupid,
should I move all my Thymleaf pages to the loadbalancer maybe , so that the a app return what it want to return with #restController then the loadbalancer use #controller to get to the styling front pages or there is a way , and for the security , should i implement the spring security with the loadbalancer instead of the A,B apps
.........................
8080 is loadBalancer port
9091 is A app port
so it seams that when A is returning the html page , the html is searching for the css at the loadbalancer machin at 8080 , while they are existing at A app on 9091
bodyToMono decodes the body but you are not handling headers.
On spring security there is very likely a redirection to the login page ... so it wont work if you only attend to the body. This might be also affecting styles somehow.
Check something like this:
How to extract response header & status code from Spring 5 WebClient ClientResponse

I am trying to get Header info from Request Controller and read into IntegrationFlow

I wanted to understand where is best location to read headers and use them inside my IntegrationFlow layer.
ServiceController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/v1/integration")
public class ServiceController {
#Autowired
private ServiceGateway gateway;
#GetMapping(value = "info")
public String info() {
return gateway.info();
}
}
ServiceGateway.java
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.MessagingGateway;
#MessagingGateway
public interface ServiceGateway {
#Gateway(requestChannel = "integration.info.gateway.channel")
public String info();
}
ServiceConfig.java
import java.net.URISyntaxException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.http.dsl.Http;
import org.springframework.messaging.MessageHeaders;
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class ServiceConfig {
#Bean
public IntegrationFlow info() throws URISyntaxException {
String uri = "http://localhost:8081/hellos/simpler";
return IntegrationFlows.from("integration.info.gateway.channel")
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST).expectedResponseType(String.class)).get();
}
}
From Consumer I am receiving some Header meta data. I want to know in above flow whether it is good idea from following approaches:
Read headers in Controller and then pass through into my IntegrationFlow: For this I am not aware how to pass through.
Is there best or any way exist to read request headers into IntegrationFlow layer?
For this second approach I have tried below code but runtime I am getting error as channel is one way and hence stopping the flow.
return IntegrationFlows.from("integration.info.gateway.channel").handle((request) -> {
MessageHeaders headers = request.getHeaders();
System.out.println("-----------" + headers);
}).handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST).expectedResponseType(String.class)).get();
My problem is how to send request parameters from incoming call to carry those internally invoking another rest call. Here I wanted to transform the data from request headers and construct into new json body and then send this to http://localhost:8081/hellos/simpler URL.
The flow:
I am trying to construct this RequestBody before sending to internal REST POST call:
A gateway method with no paylaod is for receiving data, not requesting it.
https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway-calling-no-argument-methods
Add a #Header annotated parameter to the gateway.
https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway-configuration-annotations
#MessagingGateway
public interface ServiceGateway {
#Gateway(requestChannel = "integration.info.gateway.channel")
public String info("", #Header("x-api") String xApi);
}
This will send a message with an empty string as the payload with the header set.

Unable to Login in Spring Boot

I am new to spring boot, i am trying to login in my application. I am facing some issues.
I am not able to login. It cant authenticate to login with my credential and return with message login invalid.
I want the user to be authenticate when they try to access client site (eg localhost:8080/). I also want to implement logout when user dont valid on a link file.
Here is my main application
package oidc.controller;
import eu.olympus.client.interfaces.UserClient;
import eu.olympus.model.Attribute;
import eu.olympus.model.AttributeIdentityProof;
import eu.olympus.model.Operation;
import eu.olympus.model.Policy;
import eu.olympus.model.Predicate;
import eu.olympus.model.exceptions.AuthenticationFailedException;
import eu.olympus.model.exceptions.ExistingUserException;
import eu.olympus.model.exceptions.OperationFailedException;
import eu.olympus.model.exceptions.TokenGenerationException;
import eu.olympus.model.exceptions.UserCreationFailedException;
import eu.olympus.model.server.rest.IdentityProof;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import oidc.model.AttributeContainer;
import oidc.model.ChangeAttributesRequest;
import oidc.model.ChangePasswordRequest;
import oidc.model.CreateUserRequest;
import oidc.model.DeleteAccountRequest;
import oidc.model.LoginRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
#Controller
public class OidcController {
private static final Logger logger = LoggerFactory.getLogger(OidcController.class);
#Autowired
UserClient userClient;
#Autowired
Policy policy;
// Login
#RequestMapping("/login")
public String login(Model model, #RequestParam String redirect_uri, #RequestParam String state, #RequestParam String nonce, HttpServletRequest request) {
request.getSession().setAttribute("redirectUrl", redirect_uri);
request.getSession().setAttribute("state", state);
request.getSession().setAttribute("nonce", nonce);
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("loginRequest", loginRequest);
policy.setPolicyId(nonce);
return "/login";
}
#RequestMapping("/loginFailed")
public String login(Model model) {
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("loginRequest", loginRequest);
model.addAttribute("loginError", true);
return "/login";
}
#RequestMapping("/loginPage")
public String loginPage(Model model) {
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("loginRequest", loginRequest);
model.addAttribute("hasCreated", false);
return "/login";
}
#PostMapping("/authenticate")
public RedirectView authenticate(LoginRequest loginRequest, Model model, HttpServletRequest request) throws AuthenticationFailedException, TokenGenerationException {
try {
// TODO We need to get the audience somehow?
policy.getPredicates().add(new Predicate("audience", Operation.REVEAL, new Attribute("olympus-service-provider")));
String token = userClient.authenticate(loginRequest.getUsername(), loginRequest.getPassword(), policy, null, "NONE");
model.addAttribute("username", loginRequest.getUsername());
model.addAttribute("token", token);
String redirectUrl = (String) request.getSession().getAttribute("redirectUrl");
String state = (String) request.getSession().getAttribute("state");
return new RedirectView(redirectUrl + "#state=" + state + "&id_token=" + token + "&token_type=bearer");
} catch (Exception e) {
e.printStackTrace();
if (ExceptionUtils.indexOfThrowable(e, AuthenticationFailedException.class) != -1) {
return new RedirectView("/loginFailed", true);
} else {
throw e;
}
} finally {
userClient.clearSession();
}
}
here is login Request
package oidc.model;
import lombok.Getter;
import lombok.Setter;
/**
* A container for a login request
*/
#Getter
#Setter
public class LoginRequest {
private String username;
private String password;
}
I suggest you to use Spring Security. It is a dependency and you can add it via your build tool such as Maven, Gradle etc. After studying your code what I can see is that you are trying to build security mechanism from scratch.
I wouldn't advice you to do that unless you have a high motivation factor to do so. If you can use Spring Security, it is very powerful and equipped with all the features you are looking for. You can easily overcome user authentication, authorization and even it can provide a default login page.
When it comes to authentication, you can have few types of user stores such as in-memory user store, JDBC user store, LDAP user store or even your own custom user store. Apart from username and password authentication via a GUI, you are able to do sys-to-sys authentication. You can easily achieve JWT token authentication with few steps just like adding a filter and minor configuration.
It is very difficult to cover and give the whole source code as an answer here but I will provide you a sample code such that you can get a glimpse of it. Please be advice that the below mentioned code is purely for demonstration purpose and can modify it to your standards.
Spring Security Configuration Class
package com.example.sankalpaspringbootcicd.security;
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.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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Security configuration class.
* Created date - 2021/08/02
*/
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* Authentication
* Responsible for configuring user-store.
* Overridden from WebSecurityConfigurerAdapter level.
* #param theAuthenticationManager AuthenticationManagerBuilder
* #throws Exception - Exception
*/
#Override
public void configure(AuthenticationManagerBuilder theAuthenticationManager) throws Exception {
theAuthenticationManager.inMemoryAuthentication()
//Admin user
.withUser("admin")
.password("super")
.roles("ADMIN")
.and()
//Normal user
.withUser("user")
.password("password")
.roles("USER");
}
/**
* Authorization
* Responsible for security configuration.
* Overridden from WebSecurityConfigurerAdapter level.
* #param theHttpSecurity HttpSecurity
* #throws Exception - Exception
*/
#Override
public void configure(HttpSecurity theHttpSecurity) throws Exception {
theHttpSecurity.csrf().disable();
theHttpSecurity
.authorizeRequests()
.antMatchers("/welcome/**").access("permitAll")
.antMatchers("/user/**").hasRole("ADMIN")
.anyRequest().fullyAuthenticated() //All requests should be authenticated
.and().headers().frameOptions().sameOrigin() //To allow H2-Console
.and().httpBasic();
}
/**
* Method constructing a password encoder bean.
* Constructs 'NoOpPasswordEncoder'.
* #return PasswordEncoder
*/
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
This is a very simple example and I will take you through each method. The first method is configure(AuthenticationManagerBuilder theAuthenticationManager) and what it does is basically creating you a in-memory user store.
The second method is configure(HttpSecurity theHttpSecurity) and it does allow you to do customizations to your security behaviour. It does allow some routes for everyone, restrict some routes for only with users with particular roles, allows route for H2 embedded database console, disable CSRF (Cross-Site Request Forgery) etc. This will prompt you a default login page as well. You can further add logics related to your login and logout mechanisms here as well.
The third method is PasswordEncoder getPasswordEncoder() and it does create a password encoder bean and put it in Spring Application Context to be used anytime.

How to exclude some endpoints from server.servlet.path configuration?

I have Spring Boot application with a single index.html page.
I need to have server.servlet.path=/api setting.
In order to get index.html I have to go localhost:8080/api/ becase of my setting described above.
I want to be able to get index.html by localhost:8080/ and any else endpoints by localhost:8080/api/**.
How can I do it?
Thanks
Once you configured server.servlet.path=/api, DispatcherServlet is going to handle only request matching URL Patterns /api/**.
One way to achieve your requirement is to use a plain Servlet.
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
#WebServlet(urlPatterns = {"/"})
public class RootServlet extends HttpServlet {
#Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ClassPathResource resource = new ClassPathResource("static/index.html");
String content = StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset() );
resp.getWriter().write(content);
}
}
Now you can register the Servlet using #ServletComponentScan annotation. Assuming you put RootServlet in com.myapp.servlets package:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
#SpringBootApplication
#ServletComponentScan(basePackages = "com.myapp.servlets")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
For more info see https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-add-a-servlet-filter-or-listener

Resources