diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2017.10.12__DS-3542-stateless-sessions.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2017.10.12__DS-3542-stateless-sessions.sql index 1bd954a624..802c2fa278 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2017.10.12__DS-3542-stateless-sessions.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2017.10.12__DS-3542-stateless-sessions.sql @@ -1,2 +1 @@ --- This adds an extra column to the eperson table where we save a salt for stateless authentication -ALTER TABLE eperson ADD session_salt varchar(16); \ No newline at end of file +alter table eperson add session_salt varchar(16); \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/StatusRestController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/StatusRestController.java index 75832a26be..e972d35d87 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/StatusRestController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/StatusRestController.java @@ -18,24 +18,30 @@ import java.sql.SQLException; import java.util.Collection; @RestController -@RequestMapping("/api") +@RequestMapping("/") public class StatusRestController { - @RequestMapping(value = "/status", method = RequestMethod.GET) - public Status status(HttpServletRequest request, HttpServletResponse response) throws SQLException { + @RequestMapping(value="/status", method = RequestMethod.GET) + public String status(HttpServletRequest request, HttpServletResponse response) throws SQLException { Context context = ContextUtil.obtainContext(request); //context.getDBConnection().setAutoCommit(false); // Disable autocommit. Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if(authentication != null && !(authentication.getPrincipal().equals("anonymousUser"))) { + Collection authorities = (Collection) authentication.getAuthorities(); - Collection authorities = (Collection) authentication.getAuthorities(); - - context.setCurrentUser(EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, authentication.getName())); - - EPerson current = context.getCurrentUser(); - return new Status(current); + context.setCurrentUser(EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, authentication.getName())); + EPerson current = context.getCurrentUser(); + String status = "EPerson: " + current.getEmail() + "\nFull name: " + current.getFullName() + "\nAuthorities: \n"; + for (SimpleGrantedAuthority authority: authorities) { + status += authority.getAuthority(); + } + return status; + } else { + return "Not authenticated"; + } } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java index 089c4c7a45..344093af83 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java @@ -25,14 +25,13 @@ public class CustomLogoutHandler implements LogoutHandler { public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) { Cookie cookie = WebUtils.getCookie(httpServletRequest,"access_token"); + EPerson ePerson = tokenAuthenticationService.getAuthentication(cookie.getValue(), httpServletRequest); Context context = null; try { context = ContextUtil.obtainContext(httpServletRequest); } catch (SQLException e) { log.error("Unable to obtain context", e); } - EPerson ePerson = tokenAuthenticationService.getAuthentication(cookie.getValue(), httpServletRequest, context); - ePerson.setSessionSalt(""); try { context.commit(); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index 786c3c84d2..8215b82510 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -37,16 +37,17 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider context = new Context(); String name = authentication.getName(); String password = authentication.getCredentials().toString(); + HttpServletRequest httpServletRequest = request; List grantedAuthorities = new ArrayList<>(); - int implicitStatus = authenticationService.authenticateImplicit(context, null, null, null, request); + int implicitStatus = authenticationService.authenticateImplicit(context, null, null, null, httpServletRequest); if (implicitStatus == AuthenticationMethod.SUCCESS) { log.info(LogManager.getHeader(context, "login", "type=implicit")); return new DSpaceAuthentication(name, password, grantedAuthorities); } else { - int authenticateResult = authenticationService.authenticate(context, name, password, null, request); + int authenticateResult = authenticationService.authenticate(context, name, password, null, httpServletRequest); if (AuthenticationMethod.SUCCESS == authenticateResult) { log.info(LogManager diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/JWTTokenHandler.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/JWTTokenHandler.java index 0fdae69a4b..951779b127 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/JWTTokenHandler.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/JWTTokenHandler.java @@ -10,7 +10,6 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -29,22 +28,20 @@ public class JWTTokenHandler { private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class); //TODO configurable through config files - private String jwtKey; + private static String jwtKey = "thisisatestsecretkeyforjwttokens"; private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); public static final String EPERSON_ID = "eid"; public static final String SPECIAL_GROUPS = "sg"; - - public JWTTokenHandler() { - jwtKey = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("jwt.secret", "defaultjwtkeysecret"); - System.out.println(jwtKey); - } - - public EPerson parseEPersonFromToken(String token, HttpServletRequest request, Context context) throws JOSEException, ParseException, SQLException { + public EPerson parseEPersonFromToken(String token, HttpServletRequest request) throws JOSEException, ParseException, SQLException { SignedJWT signedJWT = SignedJWT.parse(token); + Context context = new Context(); EPerson ePerson = ePersonService.find(context, UUID.fromString(signedJWT.getJWTClaimsSet().getClaim(EPERSON_ID).toString())); - String ipAddress = getIpAddress(request); + String ipAddress = request.getHeader("X-FORWARDED-FOR"); + if (ipAddress == null) { + ipAddress = request.getRemoteAddr(); + } JWSVerifier verifier = new MACVerifier(jwtKey + ePerson.getSessionSalt() + ipAddress); //If token is valid and not expired return eperson in token @@ -61,7 +58,10 @@ public class JWTTokenHandler { public String createTokenForEPerson(Context context, HttpServletRequest request, EPerson ePerson, List groups) throws JOSEException { StringKeyGenerator stringKeyGenerator = KeyGenerators.string(); String salt = stringKeyGenerator.generateKey(); - String ipAddress = getIpAddress(request); + String ipAddress = request.getHeader("X-FORWARDED-FOR"); + if (ipAddress == null) { + ipAddress = request.getRemoteAddr(); + } JWSSigner signer = new MACSigner(jwtKey + salt + ipAddress); List groupIds = groups.stream().map(group -> group.getID().toString()).collect(Collectors.toList()); @@ -87,13 +87,4 @@ public class JWTTokenHandler { return signedJWT.serialize(); } - - public String getIpAddress(HttpServletRequest request) { - String ipAddress = request.getHeader("X-FORWARDED-FOR"); - if (ipAddress == null) { - ipAddress = request.getRemoteAddr(); - } - return ipAddress; - } - } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java index 637f171277..ea9a1d8872 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java @@ -67,7 +67,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter{ } catch (SQLException e) { log.error("Unable to obtain context from request", e); } - EPerson eperson = tokenAuthenticationService.getAuthentication(token, request, context); + EPerson eperson = tokenAuthenticationService.getAuthentication(token, request); boolean isAdmin = false; try { isAdmin = authorizeService.isAdmin(context, eperson); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/TokenAuthenticationService.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/TokenAuthenticationService.java index de43415eb5..d4c9c920a7 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/TokenAuthenticationService.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/TokenAuthenticationService.java @@ -36,8 +36,8 @@ public class TokenAuthenticationService { public void addAuthentication(HttpServletRequest request, HttpServletResponse response, String email) { try { + EPerson ePerson = ePersonService.findByEmail(ContextUtil.obtainContext(request), email); Context context = ContextUtil.obtainContext(request); - EPerson ePerson = ePersonService.findByEmail(context, email); List groups = authenticationService.getSpecialGroups(context, request); String token = jwtTokenHandler.createTokenForEPerson(context, request, ePerson, groups); //TODO token is saved in a cookie, but might be better to save it in http header @@ -51,9 +51,9 @@ public class TokenAuthenticationService { } - public EPerson getAuthentication(String token, HttpServletRequest request, Context context) { + public EPerson getAuthentication(String token, HttpServletRequest request) { try { - EPerson ePerson = jwtTokenHandler.parseEPersonFromToken(token, request, context); + EPerson ePerson = jwtTokenHandler.parseEPersonFromToken(token, request); return ePerson; } catch (JOSEException e) { log.error("Jose error", e); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 993a14dd56..db4cb15f07 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -11,7 +11,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -29,6 +28,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers().cacheControl(); + + http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .exceptionHandling().and() @@ -36,16 +37,16 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { .servletApi().and() .csrf().disable() - .logout().addLogoutHandler(customLogoutHandler).logoutRequestMatcher(new AntPathRequestMatcher("/api/logout")).logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()).permitAll() + .logout().addLogoutHandler(customLogoutHandler).logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/status").permitAll() .and() .authorizeRequests() - .antMatchers( "/api/login").permitAll() - .antMatchers(HttpMethod.GET, "/api/status").permitAll() + .antMatchers(HttpMethod.POST, "/login").permitAll() + .antMatchers(HttpMethod.GET, "/status").permitAll() .and() - .addFilterBefore(new StatelessLoginFilter("/api/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new StatelessLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class) // Custom Token based authentication based on the header previously given to the client .addFilterBefore(new StatelessAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index 5b0fcab702..a059455d12 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -50,4 +50,5 @@ plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.PasswordAuthentication -jwt.secret = thisisatestsecretkeyforjwttokens \ No newline at end of file +jwt.token.secret = thisisatestsecretkeyforjwttokens +jwt.token.expiration = 1800000 \ No newline at end of file