fixed BLOCKED users are accepted

This commit is contained in:
Joern Muehlencord
2019-08-14 12:00:45 +02:00
parent 54f2e56a4c
commit 8205ffaec3
2 changed files with 306 additions and 306 deletions

View File

@ -93,7 +93,7 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter {
if (isLoggedAttempt(request, response)) {
String jwtToken = getAuthzHeader(request);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("found jwtToke in header = {}", jwtToken);
LOGGER.trace("found jwtToken in header = {}", jwtToken);
}
if (jwtToken != null) {

View File

@ -1,305 +1,305 @@
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 AND r.application = ?";
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) {
return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass()))));
}
@Override
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<>();
try {
ps = conn.prepareStatement(userRolesQuery);
ps.setString(1, username);
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
// Execute query
rs = ps.executeQuery();
// Loop over results and add each returned role to a set
while (rs.next()) {
String roleName = rs.getString(1);
// Add the role to the list of names if it isn't null
if (roleName != null) {
roleNames.add(roleName);
} else {
LOGGER.error("Null role name found while retrieving role names for user [{}]", username);
}
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return roleNames;
}
/**
* 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 {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString());
LOGGER.debug("looking up permissions for user");
}
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);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString());
}
return permissions;
}
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.isTraceEnabled()) {
LOGGER.trace("Handling JWTAuthenticationToken");
}
return true;
} else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Handling UsernamePasswordToken");
}
return false;
} else {
throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm");
}
}
@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;
}
}
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 ('BLOCKED','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 ('BLOCKED','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 AND r.application = ?";
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) {
return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass()))));
}
@Override
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<>();
try {
ps = conn.prepareStatement(userRolesQuery);
ps.setString(1, username);
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
// Execute query
rs = ps.executeQuery();
// Loop over results and add each returned role to a set
while (rs.next()) {
String roleName = rs.getString(1);
// Add the role to the list of names if it isn't null
if (roleName != null) {
roleNames.add(roleName);
} else {
LOGGER.error("Null role name found while retrieving role names for user [{}]", username);
}
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return roleNames;
}
/**
* 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 {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString());
LOGGER.debug("looking up permissions for user");
}
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);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString());
}
return permissions;
}
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.isTraceEnabled()) {
LOGGER.trace("Handling JWTAuthenticationToken");
}
return true;
} else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Handling UsernamePasswordToken");
}
return false;
} else {
throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm");
}
}
@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;
}
}