[springboot3]: Refactor websecurity logic

This commit is contained in:
Konstantinos Triantafyllou 2024-08-13 17:08:06 +03:00
parent c8187434b9
commit cb0369b1d4
21 changed files with 275 additions and 68 deletions

6
.gitignore vendored
View File

@ -54,9 +54,9 @@ atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
crashlytics.securityProperties
crashlytics-build.securityProperties
fabric.securityProperties
# Editor-based Rest Client
.idea/httpRequests

View File

@ -26,7 +26,7 @@ user's session, but with a cost of an extra http request per request.
import eu.dnetlib.uoaauthorizationlibrary.authorization.SecurityConfiguration;
@PropertySources({@PropertySource("classpath:authorization.properties")})
@PropertySources({@PropertySource("classpath:authorization.securityProperties")})
@Import(SecurityConfiguration.class)
public class Application {
public static void main(String[] args) {
@ -62,7 +62,7 @@ where session is stored.
import eu.dnetlib.uoaauthorizationlibrary.authorization.SecurityConfiguration;
@PropertySources({@PropertySource("classpath:authorization.properties")})
@PropertySources({@PropertySource("classpath:authorization.securityProperties")})
@Import(SecurityConfiguration.class)
public class Application {
public static void main(String[] args) {
@ -152,10 +152,25 @@ e.g
public Entity getEntity(@PathVariable("type") String type, @PathVariable("id") String id) {}
## Exception Handling
This library provides exception handling if an error is occurred.
### Know Http Exceptions
- UnauthorizedException (401)
- ForbiddenException (403)
- NotFoundException (404)
- ConflictException (409)
- UnprocessableException (422)
Create your own Exception with HttpStatus by extending **HttpException** class.
By default, any other Exception produces Http Status 400 (BAD REQUEST).
## Swagger configuration
This library by default includes swagger configuration, which is accessible only by PORTAL ADMIN users.
Optional set API info properties in your project configuration file:
Optional set API info securityProperties in your project configuration file:
api.title = <Title>
api.description = <Description>

View File

@ -1,14 +1,77 @@
package eu.dnetlib.uoaauthorizationlibrary;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.API;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.GlobalVars;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Properties;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.*;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableConfigurationProperties({Properties.class, GlobalVars.class, API.class})
@EnableConfigurationProperties({SecurityProperties.class, GlobalVars.class, API.class})
@ComponentScan(basePackages = {"eu.dnetlib.uoaauthorizationlibrary.authorization"})
public class AuthorizationConfiguration {
private final SecurityProperties properties;
private final GlobalVars globalVars;
@Autowired
public AuthorizationConfiguration(SecurityProperties properties, GlobalVars globalVars) {
this.properties = properties;
this.globalVars = globalVars;
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@PostConstruct
public void checkProperties() {
if(properties.getDomain() == null || properties.getDomain().isEmpty()) {
throw new RuntimeException("authorization.security.domain is missing!");
} else if(properties.getSession() == null || properties.getSession().isEmpty()) {
throw new RuntimeException("authorization.security.session is missing!");
} else if(SecurityProperties.MODE == Mode.STATELESS) {
if(properties.getUserInfoUrl() == null || properties.getUserInfoUrl().isEmpty()) {
throw new RuntimeException("authorization.security.userInfoUrl is missing!");
}
} else if(SecurityProperties.MODE == Mode.REDIS) {
Redis redis = properties.getRedis();
if (redis.getHost() == null || redis.getHost().isEmpty()) {
throw new RuntimeException("authorization.security.redis.host is missing!");
} else if (redis.getPort() == null || redis.getPort().isEmpty()) {
throw new RuntimeException("authorization.security.redis.port is missing!");
}
}
}
public Map<String, String> getProperties() {
Map<String, String> map = new HashMap<>();
map.put("authorization.security.domain", properties.getDomain());
map.put("authorization.security.session", properties.getSession());
if(SecurityProperties.MODE == Mode.STATELESS) {
map.put("authorization.security.userInfoUrl", properties.getUserInfoUrl());
}
if(SecurityProperties.MODE == Mode.REDIS) {
map.put("authorization.security.redis.host", properties.getRedis().getHost());
map.put("authorization.security.redis.port", properties.getRedis().getPort());
map.put("authorization.security.redis.password", properties.getRedis().getPassword());
}
if (GlobalVars.date != null) {
map.put("Date of deploy", GlobalVars.date.toString());
}
if (globalVars.getBuildDate() != null) {
map.put("Date of build", globalVars.getBuildDate());
}
if (globalVars.getVersion() != null) {
map.put("Version", globalVars.getVersion());
}
return map;
}
}

View File

@ -1,13 +1,10 @@
package eu.dnetlib.uoaauthorizationlibrary;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.API;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.GlobalVars;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Properties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@EnableConfigurationProperties({Properties.class, GlobalVars.class, API.class})
@ComponentScan(basePackages = {"eu.dnetlib.uoaauthorizationlibrary.authorization"}, basePackageClasses = {WebSecurityConfig.class})
@Import(AuthorizationConfiguration.class)
@ComponentScan(basePackageClasses = {WebSecurityConfig.class})
public class SecurityConfiguration { }

View File

@ -0,0 +1,5 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.configuration;
public enum Mode {
STATELESS, REDIS
}

View File

@ -2,8 +2,11 @@ package eu.dnetlib.uoaauthorizationlibrary.authorization.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("authorization.security")
public class Properties {
public class SecurityProperties {
public static Mode MODE = Mode.STATELESS;
private Redis redis = new Redis();
private String userInfoUrl;

View File

@ -1,16 +1,15 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.controllers;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.GlobalVars;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Properties;
import eu.dnetlib.uoaauthorizationlibrary.AuthorizationConfiguration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@ -18,37 +17,22 @@ import java.util.Map;
public class AuthorizationLibraryCheckDeployController {
private final Logger log = LogManager.getLogger(this.getClass());
@Autowired
private Properties securityConfig;
private final AuthorizationConfiguration configuration;
@Autowired
private GlobalVars globalVars;
public AuthorizationLibraryCheckDeployController(AuthorizationConfiguration configuration) {
this.configuration = configuration;
}
@RequestMapping(value = {"","/", "/health_check"}, method = RequestMethod.GET)
public String hello() {
public ResponseEntity<String> hello() {
log.debug("Hello from uoa-authorization-library!");
return "Hello from uoa-authorization-library!";
return ResponseEntity.ok("Hello from uoa-authorization-library!");
}
@PreAuthorize("hasAnyAuthority(@AuthorizationService.PORTAL_ADMIN)")
@RequestMapping(value = "/health_check/advanced", method = RequestMethod.GET)
public Map<String, String> checkEverything() {
Map<String, String> response = new HashMap<>();
response.put("authorization.security.redis.host", securityConfig.getRedis().getHost());
response.put("authorization.security.redis.port", securityConfig.getRedis().getPort());
response.put("authorization.security.redis.password", securityConfig.getRedis().getPassword());
response.put("authorization.security.userInfoUrl", securityConfig.getUserInfoUrl());
response.put("authorization.security.session", securityConfig.getSession());
response.put("authorization.security.domain", securityConfig.getDomain());
if(GlobalVars.date != null) {
response.put("Date of deploy", GlobalVars.date.toString());
}
if(globalVars.getBuildDate() != null) {
response.put("Date of build", globalVars.getBuildDate());
}
if (globalVars.getVersion() != null) {
response.put("Version", globalVars.getVersion());
}
return response;
public ResponseEntity<Map<String, String>> checkEverything() {
return ResponseEntity.ok(this.configuration.getProperties());
}
}

View File

@ -0,0 +1,36 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions;
import org.springframework.http.HttpStatus;
import java.util.Date;
public class ExceptionResponse {
private final long timestamp;
private final int status;
private final String error;
private final Class<?> exception;
public
ExceptionResponse(Exception exception, HttpStatus status) {
this.timestamp = new Date().getTime();
this.status = status.value();
this.error = exception.getMessage();
this.exception = exception.getClass();
}
public long getTimestamp() {
return timestamp;
}
public int getStatus() {
return status;
}
public String getError() {
return error;
}
public String getException() {
return exception.getName();
}
}

View File

@ -0,0 +1,33 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions;
import eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.http.UnauthorizedException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ExceptionsHandler {
private final Logger log = LogManager.getLogger(this.getClass());
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<?> accessDenied(AccessDeniedException ex) {
log.error(ex.getMessage(), ex);
return new ResponseEntity<>(new ExceptionResponse(ex, HttpStatus.UNAUTHORIZED), HttpStatus.UNAUTHORIZED);
}
@ExceptionHandler(HttpException.class)
public ResponseEntity<?> http(HttpException ex) {
log.error(ex.getMessage(), ex);
return new ResponseEntity<>(new ExceptionResponse(ex, ex.getHttpStatus()), ex.getHttpStatus());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> exception(Exception ex) {
log.error(ex.getMessage(), ex);
return new ResponseEntity<>(new ExceptionResponse(ex, HttpStatus.BAD_REQUEST), HttpStatus.BAD_REQUEST);
}
}

View File

@ -0,0 +1,16 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions;
import org.springframework.http.HttpStatus;
public class HttpException extends RuntimeException {
private final HttpStatus httpStatus;
public HttpException(String message, HttpStatus httpStatus) {
super(message);
this.httpStatus = httpStatus;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.http;
import eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.HttpException;
import org.springframework.http.HttpStatus;
public class ConflictException extends HttpException {
public ConflictException(String message){
super(message, HttpStatus.CONFLICT);
}
}

View File

@ -0,0 +1,9 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.http;
import eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.HttpException;
import org.springframework.http.HttpStatus;
public class ForbiddenException extends HttpException {
public ForbiddenException(String message){
super(message, HttpStatus.FORBIDDEN); }
}

View File

@ -0,0 +1,11 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.http;
import eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.HttpException;
import org.springframework.http.HttpStatus;
public class NotFoundException extends HttpException {
public NotFoundException(String message){
super(message, HttpStatus.NOT_FOUND);
}
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.http;
import eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.HttpException;
import org.springframework.http.HttpStatus;
public class UnauthorizedException extends HttpException {
public UnauthorizedException(String message){
super(message, HttpStatus.UNAUTHORIZED);
}
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.http;
import eu.dnetlib.uoaauthorizationlibrary.authorization.exceptions.HttpException;
import org.springframework.http.HttpStatus;
public class UnprocessableException extends HttpException {
public UnprocessableException(String message){
super(message, HttpStatus.UNPROCESSABLE_ENTITY);
}
}

View File

@ -1,6 +1,6 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.redis;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Properties;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.SecurityProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@ -20,21 +20,21 @@ import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class RedisConfig {
private final Properties properties;
private final SecurityProperties securityProperties;
private static final Logger logger = LogManager.getLogger(RedisConfig.class);
@Autowired
public RedisConfig(Properties properties) {
this.properties = properties;
public RedisConfig(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
@Bean
public JedisConnectionFactory connectionFactory() {
logger.info(String.format("Redis connection listens to %s:%s ", properties.getRedis().getHost(), properties.getRedis().getPort()));
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(properties.getRedis().getHost(), Integer.parseInt(properties.getRedis().getPort()));
if (properties.getRedis().getPassword() != null)
configuration.setPassword(properties.getRedis().getPassword());
logger.info(String.format("Redis connection listens to %s:%s ", securityProperties.getRedis().getHost(), securityProperties.getRedis().getPort()));
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(securityProperties.getRedis().getHost(), Integer.parseInt(securityProperties.getRedis().getPort()));
if (securityProperties.getRedis().getPassword() != null)
configuration.setPassword(securityProperties.getRedis().getPassword());
return new JedisConnectionFactory(configuration);
}
@ -50,9 +50,9 @@ public class RedisConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName(properties.getSession());
serializer.setCookieName(securityProperties.getSession());
serializer.setCookiePath("/");
serializer.setDomainName(properties.getDomain());
serializer.setDomainName(securityProperties.getDomain());
return serializer;
}
}

View File

@ -1,5 +1,7 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.redis;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Mode;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.SecurityProperties;
import eu.dnetlib.uoaauthorizationlibrary.authorization.security.AuthorizationService;
import eu.dnetlib.uoaauthorizationlibrary.authorization.security.EntryPoint;
import eu.dnetlib.uoaauthorizationlibrary.authorization.security.WebSecurity;
@ -18,6 +20,7 @@ public class RedisWebSecurity implements WebSecurity {
public RedisWebSecurity(EntryPoint entryPoint, AuthorizationService service) {
this.entryPoint = entryPoint;
this.service = service;
SecurityProperties.MODE = Mode.REDIS;
}
public HttpSecurity security(HttpSecurity http) throws Exception {

View File

@ -1,6 +1,6 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.security;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Properties;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -10,11 +10,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
private final Properties properties;
private final SecurityProperties securityProperties;
@Autowired
public CorsConfig(Properties properties) {
this.properties = properties;
public CorsConfig(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
@Bean
@ -23,7 +23,7 @@ public class CorsConfig {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*" + properties.getDomain(), "*" + properties.getDomain() + ":*")
.allowedOriginPatterns("*" + securityProperties.getDomain(), "*" + securityProperties.getDomain() + ":*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowCredentials(true);
}

View File

@ -1,6 +1,6 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.stateless;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Properties;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.SecurityProperties;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
@ -16,16 +16,17 @@ import java.util.Collections;
@Component
public class AuthorizationUtils {
private final Logger log = LogManager.getLogger(this.getClass());
private final Properties properties;
private final SecurityProperties securityProperties;
private final RestTemplate restTemplate;
@Autowired
AuthorizationUtils(Properties properties) {
this.properties = properties;
AuthorizationUtils(SecurityProperties securityProperties, RestTemplate restTemplate) {
this.securityProperties = securityProperties;
this.restTemplate = restTemplate;
}
public UserInfo getUserInfo(HttpServletRequest request) {
String url = properties.getUserInfoUrl();
RestTemplate restTemplate = new RestTemplate();
String url = securityProperties.getUserInfoUrl();
try {
if(url != null && hasCookie(request)) {
ResponseEntity<UserInfo> response = restTemplate.exchange(url, HttpMethod.GET, createHeaders(request), UserInfo.class);
@ -41,7 +42,7 @@ public class AuthorizationUtils {
private boolean hasCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if(cookies != null) {
return Arrays.stream(cookies).anyMatch(cookie -> cookie.getName().equalsIgnoreCase(this.properties.getSession()));
return Arrays.stream(cookies).anyMatch(cookie -> cookie.getName().equalsIgnoreCase(this.securityProperties.getSession()));
}
return false;
}

View File

@ -1,5 +1,7 @@
package eu.dnetlib.uoaauthorizationlibrary.authorization.stateless;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.Mode;
import eu.dnetlib.uoaauthorizationlibrary.authorization.configuration.SecurityProperties;
import eu.dnetlib.uoaauthorizationlibrary.authorization.security.AuthorizationService;
import eu.dnetlib.uoaauthorizationlibrary.authorization.security.EntryPoint;
import eu.dnetlib.uoaauthorizationlibrary.authorization.security.WebSecurity;
@ -22,15 +24,16 @@ public class StatelessWebSecurity implements WebSecurity {
this.filter = filter;
this.entryPoint = entryPoint;
this.service = service;
SecurityProperties.MODE = Mode.STATELESS;
}
@Override
public HttpSecurity security(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable);
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.addFilterBefore(filter, BasicAuthenticationFilter.class);
http.exceptionHandling(handler -> handler.authenticationEntryPoint(entryPoint));
http.authorizeHttpRequests(auth -> auth.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").hasAuthority(this.service.PORTAL_ADMIN).anyRequest().permitAll());
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http;
}

View File

@ -1,5 +1,3 @@
spring.session.store-type=none
authorization.security.userInfoUrl=
authorization.security.domain=di.uoa.gr
authorization.security.session=openAIRESession
authorization.global-vars.buildDate=@timestampAuthorizationLibrary@