mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 15:03:18 +00:00
DS-3542 status endpoint
This commit is contained in:
@@ -65,8 +65,8 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport
|
||||
@Column(name="salt", length = 32)
|
||||
private String salt;
|
||||
|
||||
@Column(name="jwt_salt", length = 16)
|
||||
private String jwtSalt;
|
||||
@Column(name="session_salt", length = 16)
|
||||
private String sessionSalt;
|
||||
|
||||
@Column(name="digest_algorithm", length = 16)
|
||||
private String digestAlgorithm;
|
||||
@@ -437,11 +437,11 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport
|
||||
return ePersonService;
|
||||
}
|
||||
|
||||
public String getJwtSalt() {
|
||||
return jwtSalt;
|
||||
public String getSessionSalt() {
|
||||
return sessionSalt;
|
||||
}
|
||||
|
||||
public void setJwtSalt(String jwtSalt) {
|
||||
this.jwtSalt = jwtSalt;
|
||||
public void setSessionSalt(String sessionSalt) {
|
||||
this.sessionSalt = sessionSalt;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1 @@
|
||||
alter table eperson add session_salt varchar(16);
|
@@ -7,12 +7,6 @@
|
||||
*/
|
||||
package org.dspace.app.rest;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.dspace.app.rest.filter.DSpaceRequestContextFilter;
|
||||
import org.dspace.app.rest.model.hateoas.DSpaceRelProvider;
|
||||
import org.dspace.app.rest.utils.ApplicationConfig;
|
||||
@@ -42,6 +36,13 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Define the Spring Boot Application settings itself. This class takes the place
|
||||
* of a web.xml file, and configures all Filters/Listeners as methods (see below).
|
||||
@@ -126,7 +127,7 @@ public class Application extends SpringBootServletInitializer {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestContextListener requestContextListener() {
|
||||
public RequestContextListener requestContextListener(){
|
||||
return new RequestContextListener();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,47 @@
|
||||
package org.dspace.app.rest;
|
||||
|
||||
|
||||
import org.dspace.app.rest.utils.ContextUtil;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/status")
|
||||
public class LoginRestController {
|
||||
|
||||
@Autowired
|
||||
private HttpServletRequest request;
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
public String status() throws SQLException {
|
||||
|
||||
Context context = ContextUtil.obtainContext(request);
|
||||
//context.getDBConnection().setAutoCommit(false); // Disable autocommit.
|
||||
|
||||
//TODO this is the implementation from Resource.createContext()
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if(authentication != null)
|
||||
{
|
||||
Collection<SimpleGrantedAuthority> specialGroups = (Collection<SimpleGrantedAuthority>) authentication.getAuthorities();
|
||||
for (SimpleGrantedAuthority grantedAuthority : specialGroups) {
|
||||
context.setSpecialGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, grantedAuthority.getAuthority()).getID());
|
||||
}
|
||||
context.setCurrentUser(EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, authentication.getName()));
|
||||
}
|
||||
EPerson current = context.getCurrentUser();
|
||||
return "EPerson: " + current.getEmail() + "\nFull name: " + current.getFullName();
|
||||
}
|
||||
|
||||
}
|
@@ -1,42 +1,114 @@
|
||||
package org.dspace.app.rest.security;
|
||||
|
||||
import org.dspace.authenticate.AuthenticationMethod;
|
||||
import org.dspace.authenticate.factory.AuthenticateServiceFactory;
|
||||
import org.dspace.authenticate.service.AuthenticationService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class EPersonRestAuthenticationProvider implements AuthenticationProvider{
|
||||
|
||||
private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
protected AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance().getAuthenticationService();
|
||||
private static final Logger log = LoggerFactory.getLogger(EPersonRestAuthenticationProvider.class);
|
||||
|
||||
@Autowired
|
||||
private HttpServletRequest request;
|
||||
|
||||
|
||||
// public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
// String email = authentication.getName();
|
||||
// Context context = new Context();
|
||||
// EPerson ePerson = null;
|
||||
// try {
|
||||
// ePerson = ePersonService.findByEmail(context, email);
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// if (ePerson != null) {
|
||||
// String password = authentication.getCredentials().toString();
|
||||
// if (ePersonService.checkPassword(context, ePerson, password)) {
|
||||
// return new UsernamePasswordAuthenticationToken(ePerson.getEmail(), password, new ArrayList<>());
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
String email = authentication.getName();
|
||||
Context context = new Context();
|
||||
EPerson ePerson = null;
|
||||
Context context = null;
|
||||
|
||||
try {
|
||||
ePerson = ePersonService.findByEmail(context, email);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (ePerson != null) {
|
||||
context = new Context();
|
||||
String name = authentication.getName();
|
||||
String password = authentication.getCredentials().toString();
|
||||
if (ePersonService.checkPassword(context, ePerson, password)) {
|
||||
return new UsernamePasswordAuthenticationToken(ePerson.getEmail(), password, new ArrayList<>());
|
||||
HttpServletRequest httpServletRequest = request;
|
||||
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();
|
||||
|
||||
|
||||
int implicitStatus = authenticationService.authenticateImplicit(context, null, null, null, httpServletRequest);
|
||||
|
||||
if (implicitStatus == AuthenticationMethod.SUCCESS) {
|
||||
log.info(LogManager.getHeader(context, "login", "type=implicit"));
|
||||
addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities);
|
||||
return new UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
|
||||
} else {
|
||||
int authenticateResult = authenticationService.authenticate(context, name, password, null, httpServletRequest);
|
||||
if (AuthenticationMethod.SUCCESS == authenticateResult) {
|
||||
addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities);
|
||||
log.info(LogManager
|
||||
.getHeader(context, "login", "type=explicit"));
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
|
||||
} else {
|
||||
log.info(LogManager.getHeader(context, "failed_login", "email="
|
||||
+ name + ", result="
|
||||
+ authenticateResult));
|
||||
throw new BadCredentialsException("Login failed");
|
||||
}
|
||||
}
|
||||
} catch (BadCredentialsException e)
|
||||
{
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Error while authenticating in the rest api", e);
|
||||
} finally {
|
||||
if (context != null && context.isValid()) {
|
||||
try {
|
||||
context.complete();
|
||||
} catch (SQLException e) {
|
||||
log.error(e.getMessage() + " occurred while trying to close", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void addSpecialGroupsToGrantedAuthorityList(Context context, HttpServletRequest httpServletRequest, List<SimpleGrantedAuthority> grantedAuthorities) throws SQLException {
|
||||
List<Group> groups = authenticationService.getSpecialGroups(context, httpServletRequest);
|
||||
for (Group group : groups) {
|
||||
grantedAuthorities.add(new SimpleGrantedAuthority(group.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return authentication.equals(UsernamePasswordAuthenticationToken.class);
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import org.dspace.eperson.service.EPersonService;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
@@ -27,14 +28,22 @@ public class JWTTokenHandler {
|
||||
private static String jwtKey = "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest";
|
||||
private AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance().getAuthenticationService();
|
||||
private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
public static final String EPERSON_ID = "eid";
|
||||
public static final String SPECIAL_GROUPS = "sg";
|
||||
|
||||
public EPerson parseEPersonFromToken(String token) 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("EPersonID").toString()));
|
||||
JWSVerifier verifier = new MACVerifier(jwtKey + ePerson.getJwtSalt());
|
||||
if (signedJWT.verify(verifier)) {
|
||||
EPerson ePerson = ePersonService.find(context, UUID.fromString(signedJWT.getJWTClaimsSet().getClaim(EPERSON_ID).toString()));
|
||||
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
|
||||
if (signedJWT.verify(verifier) && new Date().getTime() < signedJWT.getJWTClaimsSet().getExpirationTime().getTime()) {
|
||||
return ePerson;
|
||||
} else {
|
||||
return null;
|
||||
@@ -42,13 +51,17 @@ public class JWTTokenHandler {
|
||||
}
|
||||
|
||||
|
||||
public String createTokenForEPerson(Context context, EPerson ePerson, List<Group> groups) throws JOSEException {
|
||||
public String createTokenForEPerson(Context context, HttpServletRequest request, EPerson ePerson, List<Group> groups) throws JOSEException {
|
||||
StringKeyGenerator stringKeyGenerator = KeyGenerators.string();
|
||||
String salt = stringKeyGenerator.generateKey();
|
||||
JWSSigner signer = new MACSigner(jwtKey + salt);
|
||||
String ipAddress = request.getHeader("X-FORWARDED-FOR");
|
||||
if (ipAddress == null) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
}
|
||||
JWSSigner signer = new MACSigner(jwtKey + salt + ipAddress);
|
||||
|
||||
List<Integer> groupIds = groups.stream().map(Group::getLegacyId).collect(Collectors.toList());
|
||||
ePerson.setJwtSalt(salt);
|
||||
List<String> groupIds = groups.stream().map(group -> group.getID().toString()).collect(Collectors.toList());
|
||||
ePerson.setSessionSalt(salt);
|
||||
try {
|
||||
context.commit();
|
||||
} catch (SQLException e) {
|
||||
@@ -56,8 +69,9 @@ public class JWTTokenHandler {
|
||||
}
|
||||
|
||||
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
|
||||
.claim("EPersonID", ePerson.getID().toString())
|
||||
.claim("special_groups", groupIds)
|
||||
.claim(EPERSON_ID, ePerson.getID().toString())
|
||||
.claim(SPECIAL_GROUPS, groupIds)
|
||||
//TODO Expiration time configurable in config
|
||||
.expirationTime(new Date(new Date().getTime() + 60 * 1000))
|
||||
.build();
|
||||
|
||||
|
@@ -32,7 +32,6 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter{
|
||||
|
||||
Cookie cookie = WebUtils.getCookie(req,"access_token");
|
||||
|
||||
|
||||
if (cookie == null ) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
@@ -48,9 +47,9 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter{
|
||||
|
||||
if (token != null) {
|
||||
// parse the token.
|
||||
EPerson eperson = tokenAuthenticationService.getAuthentication(token);
|
||||
EPerson eperson = tokenAuthenticationService.getAuthentication(token, request);
|
||||
if (eperson != null) {
|
||||
return new UsernamePasswordAuthenticationToken(eperson, null, new ArrayList<>());
|
||||
return new UsernamePasswordAuthenticationToken(eperson.getEmail(), null, new ArrayList<>());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter
|
||||
FilterChain chain,
|
||||
Authentication auth) throws IOException, ServletException {
|
||||
|
||||
//TODO every time we log in a new token and salt is created, might need to change this
|
||||
tokenAuthenticationService.addAuthentication(req, res, auth.getName());
|
||||
|
||||
}
|
||||
|
@@ -35,7 +35,8 @@ public class TokenAuthenticationService {
|
||||
EPerson ePerson = ePersonService.findByEmail(ContextUtil.obtainContext(request), email);
|
||||
Context context = ContextUtil.obtainContext(request);
|
||||
List<Group> groups = authenticationService.getSpecialGroups(context, request);
|
||||
String token = jwtTokenHandler.createTokenForEPerson(context, ePerson, groups);
|
||||
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
|
||||
Cookie cookie = new Cookie("access_token", token);
|
||||
response.addCookie(cookie);
|
||||
} catch (JOSEException e) {
|
||||
@@ -46,9 +47,10 @@ public class TokenAuthenticationService {
|
||||
|
||||
}
|
||||
|
||||
public EPerson getAuthentication(String token) {
|
||||
public EPerson getAuthentication(String token, HttpServletRequest request) {
|
||||
try {
|
||||
return jwtTokenHandler.parseEPersonFromToken(token);
|
||||
EPerson ePerson = jwtTokenHandler.parseEPersonFromToken(token, request);
|
||||
return ePerson;
|
||||
} catch (JOSEException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ParseException e) {
|
||||
|
@@ -9,6 +9,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
|
||||
@@ -25,22 +26,21 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
http.headers().cacheControl();
|
||||
|
||||
http
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
.exceptionHandling().and()
|
||||
.anonymous().and()
|
||||
.servletApi().and()
|
||||
.csrf().disable()
|
||||
.authorizeRequests()
|
||||
|
||||
.antMatchers(HttpMethod.GET, "/").permitAll()
|
||||
.antMatchers(HttpMethod.POST, "/").permitAll()
|
||||
.antMatchers(HttpMethod.POST, "/api").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/api").permitAll()
|
||||
.antMatchers(HttpMethod.POST, "/api/login").permitAll()
|
||||
|
||||
.antMatchers(HttpMethod.POST, "/login").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/status").authenticated()
|
||||
|
||||
|
||||
.anyRequest().authenticated().and()
|
||||
.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);
|
||||
|
Reference in New Issue
Block a user