001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.shiro.authc; 018 019import org.apache.activemq.broker.ConnectionContext; 020import org.apache.activemq.command.ConnectionInfo; 021import org.apache.activemq.security.SecurityContext; 022import org.apache.activemq.shiro.ConnectionReference; 023import org.apache.activemq.shiro.env.EnvironmentFilter; 024import org.apache.activemq.shiro.subject.ConnectionSubjectResolver; 025import org.apache.activemq.shiro.subject.SubjectConnectionReference; 026import org.apache.activemq.shiro.subject.SubjectSecurityContext; 027import org.apache.shiro.authc.AuthenticationException; 028import org.apache.shiro.authc.AuthenticationToken; 029import org.apache.shiro.subject.Subject; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * The {@code AuthenticationFilter} enforces if authentication is required before allowing the broker filter chain 035 * to continue. 036 * <p/> 037 * This implementation performs a connection-level authentication assertion: If the {@link Subject} associated with the 038 * connection<b>*</b> is not authenticated, and the 039 * {@link AuthenticationPolicy AuthenticationPolicy} requires the {@code Subject} to be authenticated, it will attempt 040 * to {@link Subject#login(org.apache.shiro.authc.AuthenticationToken) login} the Subject automatically. The 041 * {@link AuthenticationToken} used to login is created by the 042 * {@link #getAuthenticationTokenFactory() authenticationTokenFactory}, typically by acquiring any credentials 043 * associated with the connection. 044 * <p/> 045 * Once the connection's {@code Subject} is authenticated as necessary, the broker filter chain will continue 046 * as expected. 047 * <p/> 048 * <b>*</b>: The upstream {@link org.apache.activemq.shiro.subject.SubjectFilter} is expected to execute before this one, ensuring a Subject instance 049 * is already associated with the connection. 050 * 051 * @since 5.10.0 052 */ 053public class AuthenticationFilter extends EnvironmentFilter { 054 055 private static final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class); 056 057 private AuthenticationPolicy authenticationPolicy; 058 private AuthenticationTokenFactory authenticationTokenFactory; 059 060 public AuthenticationFilter() { 061 this.authenticationPolicy = new DefaultAuthenticationPolicy(); 062 this.authenticationTokenFactory = new DefaultAuthenticationTokenFactory(); 063 } 064 065 public AuthenticationPolicy getAuthenticationPolicy() { 066 return authenticationPolicy; 067 } 068 069 public void setAuthenticationPolicy(AuthenticationPolicy authenticationPolicy) { 070 this.authenticationPolicy = authenticationPolicy; 071 } 072 073 public AuthenticationTokenFactory getAuthenticationTokenFactory() { 074 return authenticationTokenFactory; 075 } 076 077 public void setAuthenticationTokenFactory(AuthenticationTokenFactory authenticationTokenFactory) { 078 this.authenticationTokenFactory = authenticationTokenFactory; 079 } 080 081 protected Subject getSubject(ConnectionReference conn) { 082 return new ConnectionSubjectResolver(conn).getSubject(); 083 } 084 085 @Override 086 public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { 087 088 if (isEnabled()) { //disabled means don't enforce authentication (i.e. allow anonymous access): 089 090 Subject subject = getSubject(new ConnectionReference(context, info, getEnvironment())); 091 092 if (!subject.isAuthenticated()) { 093 094 SubjectConnectionReference connection = new SubjectConnectionReference(context, info, getEnvironment(), subject); 095 096 if (this.authenticationPolicy.isAuthenticationRequired(connection)) { 097 AuthenticationToken token = this.authenticationTokenFactory.getAuthenticationToken(connection); 098 if (token == null) { 099 String msg = "Unable to obtain authentication credentials for newly established connection. " + 100 "Authentication is required."; 101 throw new AuthenticationException(msg); 102 } 103 //token is not null - login the current subject: 104 subject.login(token); 105 } 106 } 107 } 108 109 super.addConnection(context, info); 110 } 111 112 @Override 113 public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception { 114 try { 115 super.removeConnection(context, info, error); 116 } finally { 117 SecurityContext secCtx = context.getSecurityContext(); 118 119 if (secCtx instanceof SubjectSecurityContext) { 120 121 SubjectSecurityContext subjectSecurityContext = (SubjectSecurityContext) secCtx; 122 Subject subject = subjectSecurityContext.getSubject(); 123 124 if (subject != null) { 125 try { 126 subject.logout(); 127 } catch (Throwable t) { 128 String msg = "Unable to cleanly logout connection Subject during connection removal. This is " + 129 "unexpected but not critical: it can be safely ignored because the " + 130 "connection will no longer be used."; 131 LOG.info(msg, t); 132 } 133 } 134 } 135 } 136 } 137}