restructured code and introduced JWTAuthenticationFilter support to ensure user and roles are loaded when using JWT to sign in
This commit is contained in:
@ -76,6 +76,10 @@
|
|||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax</groupId>
|
<groupId>javax</groupId>
|
||||||
<artifactId>javaee-api</artifactId>
|
<artifactId>javaee-api</artifactId>
|
||||||
|
|||||||
@ -77,13 +77,12 @@ public class AccountProducer implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((subject.isAuthenticated() == false) && (subject.isRemembered() == false)) {
|
if ((subject.isAuthenticated() == false) && (subject.isRemembered() == false)) {
|
||||||
accountName = "web";
|
return null;
|
||||||
} else {
|
} else {
|
||||||
accountName = subject.getPrincipal().toString();
|
accountName = subject.getPrincipal().toString();
|
||||||
}
|
}
|
||||||
account = accountController.getAccountEntity(accountName, true);
|
account = accountController.getAccountEntity(accountName, true);
|
||||||
// TODO introduce locale support to account and switch
|
// TODO introduce locale support to account and switch to pre-defined locale if set
|
||||||
// to pre-defined locale if set
|
|
||||||
}
|
}
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,179 +1,216 @@
|
|||||||
package de.muehlencord.shared.account.business.account.boundary;
|
package de.muehlencord.shared.account.business.account.boundary;
|
||||||
|
|
||||||
import de.muehlencord.shared.account.business.config.boundary.ConfigService;
|
import de.muehlencord.shared.account.business.account.control.AccountControl;
|
||||||
import de.muehlencord.shared.account.business.account.control.AccountControl;
|
import de.muehlencord.shared.account.business.account.entity.Account;
|
||||||
import de.muehlencord.shared.account.business.account.entity.Account;
|
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
|
||||||
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
|
import de.muehlencord.shared.account.business.account.entity.ApiKeyEntity;
|
||||||
import de.muehlencord.shared.account.business.account.entity.ApiKeyEntity;
|
import de.muehlencord.shared.account.business.account.entity.JWTObject;
|
||||||
import de.muehlencord.shared.account.business.account.entity.JWTObject;
|
import de.muehlencord.shared.account.business.config.boundary.ConfigService;
|
||||||
import de.muehlencord.shared.account.business.config.entity.ConfigException;
|
import de.muehlencord.shared.account.business.config.entity.ConfigException;
|
||||||
import de.muehlencord.shared.account.util.AccountPU;
|
import de.muehlencord.shared.account.util.AccountPU;
|
||||||
import de.muehlencord.shared.jeeutil.jwt.JWTDecoder;
|
import de.muehlencord.shared.jeeutil.jwt.JWTDecoder;
|
||||||
import de.muehlencord.shared.jeeutil.jwt.JWTEncoder;
|
import de.muehlencord.shared.jeeutil.jwt.JWTEncoder;
|
||||||
import de.muehlencord.shared.jeeutil.jwt.JWTException;
|
import de.muehlencord.shared.jeeutil.jwt.JWTException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.ejb.Lock;
|
import javax.ejb.Lock;
|
||||||
import javax.ejb.LockType;
|
import javax.ejb.LockType;
|
||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
import javax.ejb.TransactionAttribute;
|
import javax.ejb.TransactionAttribute;
|
||||||
import javax.ejb.TransactionAttributeType;
|
import javax.ejb.TransactionAttributeType;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Joern Muehlencord <joern at muehlencord.de>
|
* @author Joern Muehlencord <joern at muehlencord.de>
|
||||||
*/
|
*/
|
||||||
@Stateless
|
@Stateless
|
||||||
public class ApiKeyService implements Serializable {
|
public class ApiKeyService implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = -6981864888118320228L;
|
private static final long serialVersionUID = -6981864888118320228L;
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyService.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@AccountPU
|
@AccountPU
|
||||||
EntityManager em;
|
EntityManager em;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AccountControl accountControl;
|
AccountControl accountControl;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConfigService configService;
|
ConfigService configService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Account account;
|
Account account;
|
||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
private String issuer;
|
private String issuer;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
if (configService == null) {
|
if (configService == null) {
|
||||||
password = null;
|
password = null;
|
||||||
issuer = null;
|
issuer = null;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
password = configService.getConfigValue("rest.password");
|
password = configService.getConfigValue("rest.password");
|
||||||
issuer = configService.getConfigValue("rest.issuer");
|
issuer = configService.getConfigValue("rest.issuer");
|
||||||
} catch (ConfigException ex) {
|
} catch (ConfigException ex) {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug(ex.toString(), ex);
|
LOGGER.debug(ex.toString(), ex);
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error(ex.toString());
|
LOGGER.error(ex.toString());
|
||||||
}
|
}
|
||||||
password = null;
|
password = null;
|
||||||
issuer = null;
|
issuer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ApiKeyEntity> getUsersApiKeys(AccountEntity account) {
|
public List<ApiKeyEntity> getUsersApiKeys(AccountEntity account) {
|
||||||
Query query = em.createNamedQuery("ApiKeyEntity.findByAccount");
|
Query query = em.createNamedQuery("ApiKeyEntity.findByAccount");
|
||||||
query.setParameter("account", account);
|
query.setParameter("account", account);
|
||||||
List<ApiKeyEntity> keys = query.getResultList();
|
List<ApiKeyEntity> keys = query.getResultList();
|
||||||
if (keys == null) {
|
if (keys == null) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
} else {
|
} else {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ApiKeyEntity> getUsersApiKeys() {
|
public List<ApiKeyEntity> getUsersApiKeys() {
|
||||||
return getUsersApiKeys(accountControl.getAccountEntity(account.getUsername(), false));
|
return getUsersApiKeys(accountControl.getAccountEntity(account.getUsername(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Lock(LockType.WRITE)
|
@Lock(LockType.WRITE)
|
||||||
public String createNewApiKey(ZonedDateTime now, short expirationInMinutes) throws ApiKeyException {
|
public String createNewApiKey(ZonedDateTime now, short expirationInMinutes) throws ApiKeyException {
|
||||||
if ((password == null || issuer == null)) {
|
if ((password == null || issuer == null)) {
|
||||||
LOGGER.error("password or issuer not set in, please validate configuration");
|
LOGGER.error("password or issuer not set in, please validate configuration");
|
||||||
}
|
}
|
||||||
Date nowDate = Date.from(now.toInstant());
|
Date nowDate = Date.from(now.toInstant());
|
||||||
String apiKeyString = RandomStringUtils.randomAscii(50);
|
String apiKeyString = RandomStringUtils.randomAscii(50);
|
||||||
|
|
||||||
ApiKeyEntity apiKey = new ApiKeyEntity();
|
ApiKeyEntity apiKey = new ApiKeyEntity();
|
||||||
apiKey.setAccount(accountControl.getAccountEntity(account.getUsername(), false));
|
apiKey.setAccount(accountControl.getAccountEntity(account.getUsername(), false));
|
||||||
apiKey.setApiKey(apiKeyString);
|
apiKey.setApiKey(apiKeyString);
|
||||||
apiKey.setIssuedOn(nowDate);
|
apiKey.setIssuedOn(nowDate);
|
||||||
apiKey.setExpiration(expirationInMinutes);
|
apiKey.setExpiration(expirationInMinutes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String jwtString = JWTEncoder.encode(password, issuer, now, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
|
String jwtString = JWTEncoder.encode(password, issuer, now, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
|
||||||
em.persist(apiKey);
|
em.persist(apiKey);
|
||||||
return jwtString;
|
return jwtString;
|
||||||
} catch (JWTException ex) {
|
} catch (JWTException ex) {
|
||||||
throw new ApiKeyException("Cannot create apiKey. Reason: " + ex.toString(), ex);
|
throw new ApiKeyException("Cannot create apiKey. Reason: " + ex.toString(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateJWT(String encodedJWT) {
|
public boolean validateJWT(String encodedJWT) {
|
||||||
JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
|
JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
|
||||||
ApiKeyEntity validKey = getValidKey(decoder.getSubject(), decoder.getUniqueId(), encodedJWT);
|
ApiKeyEntity validKey = getValidKey(decoder.getSubject(), decoder.getUniqueId(), encodedJWT);
|
||||||
return validKey != null;
|
return validKey != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiKeyEntity getValidKey(String userName, String apiKey, String authorizationHeader) {
|
private ApiKeyEntity getValidKey(String userName, String apiKey, String authorizationHeader) {
|
||||||
AccountEntity userAccount = accountControl.getAccountEntity(userName, false);
|
AccountEntity userAccount = accountControl.getAccountEntity(userName, false);
|
||||||
List<ApiKeyEntity> apiKeys = getUsersApiKeys(userAccount);
|
List<ApiKeyEntity> apiKeys = getUsersApiKeys(userAccount);
|
||||||
|
|
||||||
Iterator<ApiKeyEntity> it = apiKeys.iterator();
|
Iterator<ApiKeyEntity> it = apiKeys.iterator();
|
||||||
ApiKeyEntity keyToLogout = null;
|
ApiKeyEntity keyToLogout = null;
|
||||||
while (keyToLogout == null && it.hasNext()) {
|
while (keyToLogout == null && it.hasNext()) {
|
||||||
ApiKeyEntity key = it.next();
|
ApiKeyEntity key = it.next();
|
||||||
if (key.getApiKey().equals(apiKey)) {
|
if (key.getApiKey().equals(apiKey)) {
|
||||||
ZonedDateTime issuedOn = ZonedDateTime.ofInstant(key.getIssuedOn().toInstant(), ZoneOffset.UTC);
|
ZonedDateTime issuedOn = ZonedDateTime.ofInstant(key.getIssuedOn().toInstant(), ZoneOffset.UTC);
|
||||||
String testString;
|
String testString;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
testString = JWTEncoder.encode(password, issuer, issuedOn, key.getAccount().getUsername(), key.getApiKey(), key.getExpiration());
|
testString = JWTEncoder.encode(password, issuer, issuedOn, key.getAccount().getUsername(), key.getApiKey(), key.getExpiration());
|
||||||
if (authorizationHeader.equals(testString)) {
|
if (authorizationHeader.equals(testString)) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
} catch (JWTException ex) {
|
} catch (JWTException ex) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getJWTFromApiKey(ApiKeyEntity apiKey) throws ApiKeyException {
|
public String getJWTFromApiKey(ApiKeyEntity apiKey) throws ApiKeyException {
|
||||||
ZonedDateTime issuedAt = ZonedDateTime.ofInstant(apiKey.getIssuedOn().toInstant(), ZoneOffset.UTC);
|
ZonedDateTime issuedAt = ZonedDateTime.ofInstant(apiKey.getIssuedOn().toInstant(), ZoneOffset.UTC);
|
||||||
try {
|
try {
|
||||||
return JWTEncoder.encode(password, issuer, issuedAt, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
|
return JWTEncoder.encode(password, issuer, issuedAt, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
|
||||||
} catch (JWTException ex) {
|
} catch (JWTException ex) {
|
||||||
throw new ApiKeyException("Cannot retrieve JWT from key. Reason: " + ex.toString(), ex);
|
throw new ApiKeyException("Cannot retrieve JWT from key. Reason: " + ex.toString(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public JWTObject getJWTObject(String authorizationHeader) {
|
public JWTObject getJWTObject(String encodedJWT) {
|
||||||
JWTDecoder decoder = new JWTDecoder(password, issuer, authorizationHeader);
|
JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
|
||||||
JWTObject jwtObject = new JWTObject();
|
JWTObject jwtObject = new JWTObject();
|
||||||
jwtObject.setUserName(decoder.getSubject());
|
jwtObject.setUserName(decoder.getSubject());
|
||||||
jwtObject.setUnqiueId(decoder.getUniqueId());
|
jwtObject.setUnqiueId(decoder.getUniqueId());
|
||||||
jwtObject.setValid(true);
|
jwtObject.setValid(true);
|
||||||
return jwtObject;
|
return jwtObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
/**
|
||||||
@Transactional
|
*
|
||||||
@Lock(LockType.WRITE)
|
* @param apiKey
|
||||||
public void delete(ApiKeyEntity apiKey) {
|
* @deprecated use delete (jwtObject) instead
|
||||||
em.remove(em.merge(apiKey));
|
*/
|
||||||
}
|
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
||||||
|
@Transactional
|
||||||
}
|
@Lock(LockType.WRITE)
|
||||||
|
@Deprecated
|
||||||
|
public void delete(ApiKeyEntity apiKey) {
|
||||||
|
em.remove(em.merge(apiKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TransactionAttribute(TransactionAttributeType.REQUIRED)
|
||||||
|
@Transactional
|
||||||
|
@Lock(LockType.WRITE)
|
||||||
|
public void delete(String authorizationHeader) throws ApiKeyException {
|
||||||
|
|
||||||
|
JWTObject jwtObject = getJWTObject(authorizationHeader);
|
||||||
|
if (jwtObject.isValid()) {
|
||||||
|
String userName = jwtObject.getUserName();
|
||||||
|
|
||||||
|
ApiKeyEntity keyToLogout = getValidKey(userName, jwtObject.getUnqiueId(), authorizationHeader);
|
||||||
|
|
||||||
|
if (keyToLogout == null) {
|
||||||
|
// no valid key found - must not happen, JWTVeryfingFIlter should have catched this
|
||||||
|
// FIXME - add logging / handle this problem
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("No valid key found, probably user {} has already logged out", userName);
|
||||||
|
}
|
||||||
|
throw new ApiKeyException("No valid key found, probably user " + userName + " has already logged out");
|
||||||
|
} else {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Found matching apiKey to logout");
|
||||||
|
}
|
||||||
|
em.remove(em.merge(keyToLogout));
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Key deleted, user {} logged out from webservice", userName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ApiKeyException("Provided JWT is not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
package de.muehlencord.shared.account.business.account.control;
|
package de.muehlencord.shared.account.business.account.control;
|
||||||
|
|
||||||
import de.muehlencord.shared.account.business.account.entity.AccountException;
|
|
||||||
import de.muehlencord.shared.account.business.account.entity.AccountStatus;
|
|
||||||
import de.muehlencord.shared.account.business.mail.entity.MailException;
|
|
||||||
import de.muehlencord.shared.account.business.mail.boundary.MailService;
|
|
||||||
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
|
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
|
||||||
|
import de.muehlencord.shared.account.business.account.entity.AccountException;
|
||||||
import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity;
|
import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity;
|
||||||
import de.muehlencord.shared.account.business.application.entity.ApplicationRoleEntity;
|
import de.muehlencord.shared.account.business.account.entity.AccountStatus;
|
||||||
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
|
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
|
||||||
|
import de.muehlencord.shared.account.business.application.entity.ApplicationRoleEntity;
|
||||||
|
import de.muehlencord.shared.account.business.mail.boundary.MailService;
|
||||||
|
import de.muehlencord.shared.account.business.mail.entity.MailException;
|
||||||
import de.muehlencord.shared.account.util.AccountPU;
|
import de.muehlencord.shared.account.util.AccountPU;
|
||||||
import de.muehlencord.shared.account.util.SecurityUtil;
|
import de.muehlencord.shared.account.util.SecurityUtil;
|
||||||
import de.muehlencord.shared.util.DateUtil;
|
import de.muehlencord.shared.util.DateUtil;
|
||||||
@ -23,10 +23,10 @@ import javax.persistence.NoResultException;
|
|||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -47,7 +47,7 @@ public class AccountControl implements Serializable {
|
|||||||
@Inject
|
@Inject
|
||||||
@AccountPU
|
@AccountPU
|
||||||
EntityManager em;
|
EntityManager em;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a list of active accounts
|
* returns a list of active accounts
|
||||||
*
|
*
|
||||||
@ -156,7 +156,7 @@ public class AccountControl implements Serializable {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteAccount(AccountEntity account) throws AccountException {
|
public void deleteAccount(AccountEntity account) throws AccountException {
|
||||||
Date now = new Date(); // Todo now in UTC
|
Date now = DateUtil.getCurrentTimeInUTC();
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
String currentUserName = currentUser.getPrincipal().toString();
|
String currentUserName = currentUser.getPrincipal().toString();
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ public class AccountControl implements Serializable {
|
|||||||
|
|
||||||
String randomString = RandomStringUtils.random(40, true, true);
|
String randomString = RandomStringUtils.random(40, true, true);
|
||||||
|
|
||||||
Date validTo = new Date(); // TODO now in UTC
|
Date validTo = DateUtil.getCurrentTimeInUTC();
|
||||||
validTo = new Date(validTo.getTime() + 1000 * 600); // 10 minutes to react
|
validTo = new Date(validTo.getTime() + 1000 * 600); // 10 minutes to react
|
||||||
|
|
||||||
// TODO rework password reset
|
// TODO rework password reset
|
||||||
@ -217,7 +217,7 @@ public class AccountControl implements Serializable {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
if (account.getPasswordResetOngoing() && (account.getPasswordResetHash() != null) && (account.getPasswordResetValidTo() != null)) {
|
if (account.getPasswordResetOngoing() && (account.getPasswordResetHash() != null) && (account.getPasswordResetValidTo() != null)) {
|
||||||
Date now = new Date(); // TODO now in UTC
|
Date now = DateUtil.getCurrentTimeInUTC();
|
||||||
String storedHash = account.getPasswordResetHash().trim();
|
String storedHash = account.getPasswordResetHash().trim();
|
||||||
if (account.getPasswordResetValidTo().after(now)) {
|
if (account.getPasswordResetValidTo().after(now)) {
|
||||||
if (storedHash.equals(resetPasswordToken)) {
|
if (storedHash.equals(resetPasswordToken)) {
|
||||||
@ -248,7 +248,7 @@ public class AccountControl implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void executePasswordReset(AccountEntity account, String newPassword) {
|
private void executePasswordReset(AccountEntity account, String newPassword) {
|
||||||
Date now = new Date(); // TODO now in UTC
|
Date now = DateUtil.getCurrentTimeInUTC();
|
||||||
|
|
||||||
String hashedPassword = SecurityUtil.createPassword(newPassword);
|
String hashedPassword = SecurityUtil.createPassword(newPassword);
|
||||||
// account.setAccountPassword(hashedPassword);
|
// account.setAccountPassword(hashedPassword);
|
||||||
@ -294,7 +294,7 @@ public class AccountControl implements Serializable {
|
|||||||
public void addLoginError(AccountEntity account) {
|
public void addLoginError(AccountEntity account) {
|
||||||
// TODO reimplement
|
// TODO reimplement
|
||||||
// try {
|
// try {
|
||||||
// Date now = new Date(); // TODO now in UTC
|
// Date now = DateUtil.getCurrentTimeInUTC();
|
||||||
// account.setLastFailedLogin(now);
|
// account.setLastFailedLogin(now);
|
||||||
// account.setFailureCount(account.getFailureCount() + 1);
|
// account.setFailureCount(account.getFailureCount() + 1);
|
||||||
//
|
//
|
||||||
@ -367,5 +367,5 @@ public class AccountControl implements Serializable {
|
|||||||
em.remove(login);
|
em.remove(login);
|
||||||
em.merge(account);
|
em.merge(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,116 +1,114 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2018 Joern Muehlencord <joern at muehlencord.de>.
|
* Copyright 2018 Joern Muehlencord <joern at muehlencord.de>.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package de.muehlencord.shared.account.business.application.control;
|
package de.muehlencord.shared.account.business.application.control;
|
||||||
|
|
||||||
import de.muehlencord.shared.account.business.application.boundary.ApplicationService;
|
import de.muehlencord.shared.account.business.application.boundary.ApplicationService;
|
||||||
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
|
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.ejb.EJB;
|
import javax.ejb.EJB;
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.enterprise.inject.Produces;
|
import javax.enterprise.inject.Produces;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Joern Muehlencord <joern at muehlencord.de>
|
* @author Joern Muehlencord <joern at muehlencord.de>
|
||||||
*/
|
*/
|
||||||
@Named("applicationController")
|
@Named("applicationController")
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class ApplicationController {
|
public class ApplicationController {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationController.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationController.class);
|
||||||
|
|
||||||
@EJB
|
@EJB
|
||||||
ApplicationService applicationService;
|
ApplicationService applicationService;
|
||||||
|
|
||||||
private String version;
|
private String version;
|
||||||
private String buildDate;
|
private String buildDate;
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
private ApplicationEntity application = null;
|
private ApplicationEntity application = null;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void readBuildInfoProperties() {
|
public void readBuildInfoProperties() {
|
||||||
if (LOGGER.isTraceEnabled()) {
|
if (LOGGER.isTraceEnabled()) {
|
||||||
LOGGER.trace("Trying to read buildInfo.properties");
|
LOGGER.trace("Trying to read buildInfo.properties");
|
||||||
}
|
}
|
||||||
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("buildInfo.properties");
|
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("buildInfo.properties");
|
||||||
if (in == null) {
|
if (in == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
try {
|
try {
|
||||||
props.load(in);
|
props.load(in);
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("properties read from buildInfo.properties");
|
LOGGER.debug("properties read from buildInfo.properties");
|
||||||
}
|
}
|
||||||
|
|
||||||
version = props.getProperty("build.version");
|
version = props.getProperty("build.version");
|
||||||
buildDate = props.getProperty("build.timestamp");
|
buildDate = props.getProperty("build.timestamp");
|
||||||
uuid = UUID.fromString(props.getProperty("application.uuid"));
|
uuid = UUID.fromString(props.getProperty("application.uuid"));
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("buildInfo.properties parsed successfully");
|
LOGGER.debug("buildInfo.properties parsed successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOGGER.error("Cannot find buildInfo.properties. ", ex);
|
LOGGER.error("Cannot find buildInfo.properties. ", ex);
|
||||||
version = "??";
|
version = "??";
|
||||||
buildDate = "??";
|
buildDate = "??";
|
||||||
uuid = null;
|
uuid = null;
|
||||||
LOGGER.error("Application id not readable, application will not be able to run");
|
LOGGER.error("Application id not readable, application will not be able to run");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uuid != null) {
|
if (uuid != null) {
|
||||||
this.application = applicationService.findById(uuid);
|
this.application = applicationService.findById(uuid);
|
||||||
if (application == null) {
|
if (application == null) {
|
||||||
LOGGER.error("Could not find application with id ");
|
LOGGER.error("Could not find application with id ");
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("Found application {} with id {}", application.getApplicationName(), uuid.toString());
|
LOGGER.info("Found application {} with id {}", application.getApplicationName(), uuid.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* needs to return link to "Account UI" and not to current selected
|
* needs to return link to "Account UI" and not to current selected application
|
||||||
* application TODO: ensure only Account UI can call functions where
|
* TODO: ensure only Account UI can call functions where appliction can be handed in - all other applications need to call the function which use the injected application
|
||||||
* appliction can be handed in - all other applications need to call the
|
*/
|
||||||
* function which use the injected application
|
@Produces
|
||||||
*/
|
public ApplicationEntity getApplication() {
|
||||||
@Produces
|
return application;
|
||||||
public ApplicationEntity getApplication() {
|
|
||||||
return application;
|
}
|
||||||
|
|
||||||
}
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
public String getVersion() {
|
}
|
||||||
return version;
|
|
||||||
}
|
public String getBuildDate() {
|
||||||
|
return buildDate;
|
||||||
public String getBuildDate() {
|
}
|
||||||
return buildDate;
|
|
||||||
}
|
public UUID getApplicationId() {
|
||||||
|
return uuid;
|
||||||
public UUID getApplicationId() {
|
}
|
||||||
return uuid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,71 +1,71 @@
|
|||||||
package de.muehlencord.shared.account.business.mail.boundary;
|
package de.muehlencord.shared.account.business.mail.boundary;
|
||||||
|
|
||||||
import de.muehlencord.shared.account.business.mail.entity.MailDatamodel;
|
import de.muehlencord.shared.account.business.mail.entity.MailDatamodel;
|
||||||
import de.muehlencord.shared.account.business.mail.entity.MailTemplateException;
|
import de.muehlencord.shared.account.business.mail.entity.MailTemplateEntity;
|
||||||
import de.muehlencord.shared.account.business.mail.entity.MailTemplateEntity;
|
import de.muehlencord.shared.account.business.mail.entity.MailTemplateException;
|
||||||
import de.muehlencord.shared.account.util.AccountPU;
|
import de.muehlencord.shared.account.util.AccountPU;
|
||||||
import freemarker.cache.StringTemplateLoader;
|
import freemarker.cache.StringTemplateLoader;
|
||||||
import freemarker.template.Configuration;
|
import freemarker.template.Configuration;
|
||||||
import freemarker.template.Template;
|
import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateExceptionHandler;
|
import freemarker.template.TemplateExceptionHandler;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author jomu
|
* @author jomu
|
||||||
*/
|
*/
|
||||||
@Stateless
|
@Stateless
|
||||||
public class MailTemplateService implements Serializable {
|
public class MailTemplateService implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = -136113381443058697L;
|
private static final long serialVersionUID = -136113381443058697L;
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(MailTemplateService.class.getName());
|
private static final Logger LOGGER = LoggerFactory.getLogger(MailTemplateService.class.getName());
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@AccountPU
|
@AccountPU
|
||||||
EntityManager em;
|
EntityManager em;
|
||||||
|
|
||||||
public String getStringFromTemplate(String templateName, MailDatamodel dataModel) throws MailTemplateException {
|
public String getStringFromTemplate(String templateName, MailDatamodel dataModel) throws MailTemplateException {
|
||||||
try {
|
try {
|
||||||
Query query = em.createNamedQuery("MailTemplateEntity.findByTemplateName");
|
Query query = em.createNamedQuery("MailTemplateEntity.findByTemplateName");
|
||||||
query.setParameter("templateName", templateName);
|
query.setParameter("templateName", templateName);
|
||||||
MailTemplateEntity templateEntity = (MailTemplateEntity) query.getSingleResult();
|
MailTemplateEntity templateEntity = (MailTemplateEntity) query.getSingleResult();
|
||||||
if (templateEntity == null) {
|
if (templateEntity == null) {
|
||||||
LOGGER.error("Tempate with name " + templateName + " not found");
|
LOGGER.error("Tempate with name " + templateName + " not found");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
|
Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
|
||||||
configuration.setDefaultEncoding("UTF-8"); // FIXME make encoding configurable
|
configuration.setDefaultEncoding("UTF-8"); // FIXME - make encoding configurable
|
||||||
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||||
|
|
||||||
StringTemplateLoader stringLoader = new StringTemplateLoader();
|
StringTemplateLoader stringLoader = new StringTemplateLoader();
|
||||||
stringLoader.putTemplate(templateEntity.getTemplateName(), templateEntity.getTemplateValue());
|
stringLoader.putTemplate(templateEntity.getTemplateName(), templateEntity.getTemplateValue());
|
||||||
configuration.setTemplateLoader(stringLoader);
|
configuration.setTemplateLoader(stringLoader);
|
||||||
|
|
||||||
Template template = configuration.getTemplate(templateEntity.getTemplateName());
|
Template template = configuration.getTemplate(templateEntity.getTemplateName());
|
||||||
|
|
||||||
Writer out = new StringWriter();
|
Writer out = new StringWriter();
|
||||||
template.process(dataModel, out);
|
template.process(dataModel, out);
|
||||||
String templateString = out.toString();
|
String templateString = out.toString();
|
||||||
return templateString;
|
return templateString;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
String hint = "Error while processing template with name " + templateName + ".";
|
String hint = "Error while processing template with name " + templateName + ".";
|
||||||
LOGGER.error(hint + " " + ex.toString());
|
LOGGER.error(hint + " " + ex.toString());
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug(hint, ex);
|
LOGGER.debug(hint, ex);
|
||||||
}
|
}
|
||||||
throw new MailTemplateException(hint, ex);
|
throw new MailTemplateException(hint, ex);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package de.muehlencord.shared.account.shiro.authc;
|
||||||
|
|
||||||
|
import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
|
||||||
|
import de.muehlencord.shared.account.business.account.entity.JWTObject;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.credential.CredentialsMatcher;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author joern.muehlencord
|
||||||
|
*/
|
||||||
|
public class JwtMatcher implements CredentialsMatcher {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JwtMatcher.class);
|
||||||
|
private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
|
||||||
|
if (token == null) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("No token available - cannot match credentials");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((info == null) || (info.getCredentials() == null)) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("No authenticationInfo available - cannot match credentials");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object submittedJwtObj = token.getCredentials();
|
||||||
|
Object storedCredentials = getStoredPassword(info);
|
||||||
|
if ((submittedJwtObj != null) && (submittedJwtObj.getClass().isAssignableFrom(String.class))) {
|
||||||
|
String submittedJwt = (String) submittedJwtObj;
|
||||||
|
if (apiKeyService.validateJWT(submittedJwt)) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("JWT is valid, checking if it comes from the correct user");
|
||||||
|
}
|
||||||
|
|
||||||
|
JWTObject jwtObject = apiKeyService.getJWTObject(submittedJwt);
|
||||||
|
String storedUsername = info.getPrincipals().getPrimaryPrincipal().toString();
|
||||||
|
if (jwtObject.getUserName().equals(storedUsername)) {
|
||||||
|
if (jwtObject.getUnqiueId().equals (storedCredentials)) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("JWT matches user and password is correct");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("JWT password does not match provided password");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("JWT belongs to user {}, but authinfo is from user {} - JWT does not match");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("JWT is invalid");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Unexpected authInfoFormat - please check your configuration");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object getStoredPassword(AuthenticationInfo storedAccountInfo) {
|
||||||
|
Object stored = storedAccountInfo != null ? storedAccountInfo.getCredentials() : null;
|
||||||
|
//fix for https://issues.apache.org/jira/browse/SHIRO-363
|
||||||
|
if (stored instanceof char[]) {
|
||||||
|
stored = new String((char[]) stored);
|
||||||
|
}
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - can this be injected?
|
||||||
|
private ApiKeyService lookupApiKeyServiceBean() {
|
||||||
|
try {
|
||||||
|
Context c = new InitialContext();
|
||||||
|
return (ApiKeyService) c.lookup("java:module/ApiKeyService"); // NOI18N
|
||||||
|
} catch (NamingException ex) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug(ex.toString(), ex);
|
||||||
|
} else {
|
||||||
|
LOGGER.error(ex.toString());
|
||||||
|
}
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
package de.muehlencord.shared.account.shiro.filter;
|
||||||
|
|
||||||
|
import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
|
||||||
|
import de.muehlencord.shared.account.business.account.entity.JWTObject;
|
||||||
|
import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import javax.json.Json;
|
||||||
|
import javax.json.JsonObject;
|
||||||
|
import javax.json.JsonReader;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
|
||||||
|
import org.apache.shiro.web.util.WebUtils;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Joern Muehlencord <joern at muehlencord.de>
|
||||||
|
*/
|
||||||
|
public final class JWTAuthenticationFilter extends AuthenticatingFilter {
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
|
||||||
|
protected static final String AUTHORIZATION_HEADER = "Authorization"; // NOI18N
|
||||||
|
|
||||||
|
public static final String USERNAME = "username";
|
||||||
|
public static final String PASSWORD = "password";
|
||||||
|
private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
|
||||||
|
|
||||||
|
public JWTAuthenticationFilter() {
|
||||||
|
// FIXME - logging in via JWTAuthenticationFilter does not yet work. Need to set login to anonymous to execute login in rest service
|
||||||
|
setLoginUrl("/rest/account/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
boolean loggedIn = false;
|
||||||
|
|
||||||
|
if (isLoginRequest(request, response) || isLoggedAttempt(request, response)) {
|
||||||
|
loggedIn = executeLogin(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loggedIn) {
|
||||||
|
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||||
|
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
if (isLoginRequest(request, response)) {
|
||||||
|
String json = IOUtils.toString(request.getInputStream(), "UTF-8"); // FIXME - check charset in request
|
||||||
|
|
||||||
|
if (json != null && !json.isEmpty()) {
|
||||||
|
try (JsonReader jr = Json.createReader(new StringReader(json))) {
|
||||||
|
JsonObject object = jr.readObject();
|
||||||
|
String username = object.getString(USERNAME);
|
||||||
|
String password = object.getString(PASSWORD);
|
||||||
|
return new UsernamePasswordToken(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoggedAttempt(request, response)) {
|
||||||
|
String jwtToken = getAuthzHeader(request);
|
||||||
|
if (jwtToken != null) {
|
||||||
|
return createToken(jwtToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UsernamePasswordToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isLoggedAttempt(ServletRequest request, ServletResponse response) {
|
||||||
|
String authzHeader = getAuthzHeader(request);
|
||||||
|
return authzHeader != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAuthzHeader(ServletRequest request) {
|
||||||
|
HttpServletRequest httpRequest = WebUtils.toHttp(request);
|
||||||
|
return httpRequest.getHeader(AUTHORIZATION_HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JWTAuthenticationToken createToken(String token) {
|
||||||
|
if (apiKeyService.validateJWT(token)) {
|
||||||
|
JWTObject jwtObject = apiKeyService.getJWTObject(token);
|
||||||
|
return new JWTAuthenticationToken(jwtObject.getUserName(), token);
|
||||||
|
} else {
|
||||||
|
throw new AuthenticationException("provided API key invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - can this be injected?
|
||||||
|
private ApiKeyService lookupApiKeyServiceBean() {
|
||||||
|
try {
|
||||||
|
Context c = new InitialContext();
|
||||||
|
return (ApiKeyService) c.lookup("java:module/ApiKeyService"); // NOI18N
|
||||||
|
} catch (NamingException ex) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug(ex.toString(), ex);
|
||||||
|
} else {
|
||||||
|
LOGGER.error(ex.toString());
|
||||||
|
}
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,254 @@
|
|||||||
|
package de.muehlencord.shared.account.shiro.realm;
|
||||||
|
|
||||||
|
import de.muehlencord.shared.account.shiro.authc.JwtMatcher;
|
||||||
|
import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.apache.shiro.authc.AccountException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.IncorrectCredentialsException;
|
||||||
|
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.UnknownAccountException;
|
||||||
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
|
||||||
|
import org.apache.shiro.authc.credential.CredentialsMatcher;
|
||||||
|
import org.apache.shiro.realm.jdbc.JdbcRealm;
|
||||||
|
import org.apache.shiro.util.JdbcUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author joern.muehlencord
|
||||||
|
*/
|
||||||
|
public class AccountRealm extends JdbcRealm {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(AccountRealm.class);
|
||||||
|
|
||||||
|
protected String applicationId = null;
|
||||||
|
protected String jwtAuthenticationQuery = "select ak.api_key from account a, api_key ak where ak.account = a.id and a.username = ? and a.status not in ('LOCKED','DELETED','DISABLED') ORDER BY ak.issued_on ASC";
|
||||||
|
protected CredentialsMatcher jwtMatcher = new JwtMatcher();
|
||||||
|
|
||||||
|
public AccountRealm() {
|
||||||
|
this.authenticationQuery = "select al.account_password from account a, account_login al where al.account = a.id and a.username = ? and status not in ('LOCKED','DELETED','DISABLED')";
|
||||||
|
this.userRolesQuery = "select r.role_name from application_role r, account_role ar, account a WHERE a.username = ? AND a.id = ar.account AND ar.account_role = r.id";
|
||||||
|
this.permissionsQuery = "select permission_name from application_role appr, role_permission rp, application_permission appp WHERE appr.role_name = ? AND appr.application = ? AND rp.application_role = appr.id AND rp.role_permission = appp.id";
|
||||||
|
this.permissionsLookupEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
super.supports(token);
|
||||||
|
return token != null && (token instanceof JWTAuthenticationToken || token instanceof UsernamePasswordToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isJwtAuthentication(AuthenticationToken token) {
|
||||||
|
if (token == null) {
|
||||||
|
throw new AuthenticationException("empty tokens are not supported by this realm");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.getClass().isAssignableFrom(JWTAuthenticationToken.class)) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Handling JWTAuthenticationToken");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Handling UsernamePasswordToken");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overwritten getPermissions. Only change is to inject the applicationId
|
||||||
|
* into the the query
|
||||||
|
*
|
||||||
|
* @param conn the connection to use
|
||||||
|
* @param username the user to lookup
|
||||||
|
* @param roleNames the users roles
|
||||||
|
* @return a list of permissions
|
||||||
|
* @throws SQLException if the SQL query fails
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
Set<String> permissions = new LinkedHashSet<>();
|
||||||
|
try {
|
||||||
|
ps = conn.prepareStatement(permissionsQuery);
|
||||||
|
for (String roleName : roleNames) {
|
||||||
|
|
||||||
|
ps.setString(1, roleName);
|
||||||
|
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
|
||||||
|
|
||||||
|
ResultSet rs = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Execute query
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
|
||||||
|
// Loop over results and add each returned role to a set
|
||||||
|
while (rs.next()) {
|
||||||
|
|
||||||
|
String permissionString = rs.getString(1);
|
||||||
|
|
||||||
|
// Add the permission to the set of permissions
|
||||||
|
permissions.add(permissionString);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeResultSet(rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeStatement(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
||||||
|
if (isJwtAuthentication(token)) {
|
||||||
|
return doGetJwtAuthenticationInfo(token);
|
||||||
|
} else {
|
||||||
|
return super.doGetAuthenticationInfo(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AuthenticationInfo doGetJwtAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
||||||
|
JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token;
|
||||||
|
String username = jwtToken.getUserName();
|
||||||
|
|
||||||
|
// Null username is invalid
|
||||||
|
if (username == null) {
|
||||||
|
throw new AccountException("Null usernames are not allowed by this realm.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Checking JWT for user {}", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection conn = null;
|
||||||
|
SimpleAuthenticationInfo info = null;
|
||||||
|
try {
|
||||||
|
conn = dataSource.getConnection();
|
||||||
|
|
||||||
|
String apiKey = getApiKeyForJwtUser(conn, username);
|
||||||
|
|
||||||
|
if (apiKey == null) {
|
||||||
|
throw new UnknownAccountException("No api key found for user [" + username + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
info = new SimpleAuthenticationInfo(username, apiKey.toCharArray(), getName());
|
||||||
|
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
final String message = "There was a SQL error while authenticating user [" + username + "]";
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug(message, ex);
|
||||||
|
} else {
|
||||||
|
LOGGER.error(ex.toString());
|
||||||
|
}
|
||||||
|
// Rethrow any SQL errors as an authentication exception
|
||||||
|
throw new AuthenticationException(message, ex);
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeConnection(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getApiKeyForJwtUser(Connection conn, String username) throws SQLException {
|
||||||
|
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
ps = conn.prepareStatement(jwtAuthenticationQuery);
|
||||||
|
ps.setString(1, username);
|
||||||
|
|
||||||
|
// Execute query
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
// loop through result, take last one (by default ordered by issue date ASC)
|
||||||
|
// we only expect one - application should delete all obsolete ones automatically
|
||||||
|
while (rs.next()) {
|
||||||
|
result = rs.getString(1);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
JdbcUtils.closeResultSet(rs);
|
||||||
|
JdbcUtils.closeStatement(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the submitted {@code AuthenticationToken}'s credentials
|
||||||
|
* match the stored account {@code AuthenticationInfo}'s credentials, and if
|
||||||
|
* not, throws an {@link AuthenticationException}.
|
||||||
|
*
|
||||||
|
* @param token the submitted authentication token
|
||||||
|
* @param info the AuthenticationInfo corresponding to the given
|
||||||
|
* {@code token}
|
||||||
|
* @throws AuthenticationException if the token's credentials do not match
|
||||||
|
* the stored account credentials.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
|
||||||
|
CredentialsMatcher cm;
|
||||||
|
if (isJwtAuthentication(token)) {
|
||||||
|
cm = getJwtMatcher();
|
||||||
|
} else {
|
||||||
|
cm = getCredentialsMatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cm != null) {
|
||||||
|
if (!cm.doCredentialsMatch(token, info)) {
|
||||||
|
//not successful - throw an exception to indicate this:
|
||||||
|
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
|
||||||
|
throw new IncorrectCredentialsException(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify "
|
||||||
|
+ "credentials during authentication. If you do not wish for credentials to be examined, you "
|
||||||
|
+ "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *** getter / setter *** */
|
||||||
|
public String getApplicationId() {
|
||||||
|
return this.applicationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicationId(String applicationId) {
|
||||||
|
this.applicationId = applicationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJwtAuthenticationQuery() {
|
||||||
|
return jwtAuthenticationQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwtAuthenticationQuery(String jwtAuthenticationQuery) {
|
||||||
|
this.jwtAuthenticationQuery = jwtAuthenticationQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialsMatcher getJwtMatcher() {
|
||||||
|
return jwtMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwtMatcher(CredentialsMatcher jwtMatcher) {
|
||||||
|
this.jwtMatcher = jwtMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.muehlencord.shared.account.util;
|
package de.muehlencord.shared.account.shiro.realm;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package de.muehlencord.shared.account.shiro.token;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author joern.muehlencord
|
||||||
|
*/
|
||||||
|
public class JWTAuthenticationToken implements AuthenticationToken {
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
public JWTAuthenticationToken(String userId, String token) {
|
||||||
|
this.userName = userId;
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrincipal() {
|
||||||
|
return getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCredentials() {
|
||||||
|
return getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userId) {
|
||||||
|
this.userName = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package de.muehlencord.shared.account.util;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author joern.muehlencord
|
||||||
|
*/
|
||||||
|
public class AccountSecurityExceptionMapper implements ExceptionMapper<AccountSecurityException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(AccountSecurityException ex) {
|
||||||
|
return Response.status(Response.Status.UNAUTHORIZED).entity(ex.getMessage()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,12 +1,12 @@
|
|||||||
package de.muehlencord.shared.account.util;
|
package de.muehlencord.shared.account.util;
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.apache.shiro.authc.credential.DefaultPasswordService;
|
import org.apache.shiro.authc.credential.DefaultPasswordService;
|
||||||
import org.apache.shiro.crypto.hash.DefaultHashService;
|
import org.apache.shiro.crypto.hash.DefaultHashService;
|
||||||
import org.apache.shiro.crypto.hash.Sha512Hash;
|
import org.apache.shiro.crypto.hash.Sha512Hash;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,53 +1,53 @@
|
|||||||
package de.muehlencord.shared.account.util;
|
package de.muehlencord.shared.account.shiro.realm;
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.authc.AuthenticationException;
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
import org.apache.shiro.config.IniSecurityManagerFactory;
|
import org.apache.shiro.config.IniSecurityManagerFactory;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.apache.shiro.mgt.SecurityManager;
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
import static org.junit.Assume.assumeNotNull;
|
import static org.junit.Assume.assumeNotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Joern Muehlencord <joern at muehlencord.de>
|
* @author Joern Muehlencord <joern at muehlencord.de>
|
||||||
*/
|
*/
|
||||||
public class UserNameActiveDirectoryRealmTest {
|
public class UserNameActiveDirectoryRealmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUsernameLogin() {
|
public void testUsernameLogin() {
|
||||||
String userName = "user.name";
|
String userName = "user.name";
|
||||||
String password = "secret";
|
String password = "secret";
|
||||||
testLogin(userName, password);
|
testLogin(userName, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmailaddressLogin() {
|
public void testEmailaddressLogin() {
|
||||||
String userName = "user.name@domain.com";
|
String userName = "user.name@domain.com";
|
||||||
String password = "secret";
|
String password = "secret";
|
||||||
testLogin(userName, password);
|
testLogin(userName, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=AuthenticationException.class)
|
@Test(expected=AuthenticationException.class)
|
||||||
public void testWrongUserNamePassword() {
|
public void testWrongUserNamePassword() {
|
||||||
String userName = "test123";
|
String userName = "test123";
|
||||||
String password = "secret";
|
String password = "secret";
|
||||||
testLogin(userName, password);
|
testLogin(userName, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testLogin(String userName, String password) throws AuthenticationException {
|
private void testLogin(String userName, String password) throws AuthenticationException {
|
||||||
assumeNotNull(UserNameActiveDirectoryRealmTest.class.getResource("/shiro.ini"));
|
assumeNotNull(UserNameActiveDirectoryRealmTest.class.getResource("/shiro.ini"));
|
||||||
|
|
||||||
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
|
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
|
||||||
SecurityManager securityManager = factory.getInstance();
|
SecurityManager securityManager = factory.getInstance();
|
||||||
SecurityUtils.setSecurityManager(securityManager);
|
SecurityUtils.setSecurityManager(securityManager);
|
||||||
|
|
||||||
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
|
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
|
|
||||||
currentUser.login(token);
|
currentUser.login(token);
|
||||||
System.out.println("Logged in");
|
System.out.println("Logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user