All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tynamo.security.services.SecurityModule Maven / Gradle / Ivy

There is a newer version: 0.9.0
Show newest version
package org.tynamo.security.services;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import org.apache.shiro.ShiroException;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.Serializer;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.MethodAdviceReceiver;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Contribute;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Marker;
import org.apache.tapestry5.ioc.annotations.Match;
import org.apache.tapestry5.ioc.annotations.Order;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.apache.tapestry5.services.ApplicationInitializer;
import org.apache.tapestry5.services.ApplicationInitializerFilter;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.ComponentRequestFilter;
import org.apache.tapestry5.services.Context;
import org.apache.tapestry5.services.Core;
import org.apache.tapestry5.services.Environment;
import org.apache.tapestry5.services.HttpServletRequestFilter;
import org.apache.tapestry5.services.LibraryMapping;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.slf4j.Logger;
import org.tynamo.security.Security;
import org.tynamo.security.SecurityComponentRequestFilter;
import org.tynamo.security.SecuritySymbols;
import org.tynamo.security.ShiroAnnotationWorker;
import org.tynamo.security.internal.ModularRealmAuthenticator;
import org.tynamo.security.internal.SecurityExceptionHandlerAssistant;
import org.tynamo.security.internal.services.LoginContextService;
import org.tynamo.security.internal.services.impl.LoginContextServiceImpl;
import org.tynamo.security.services.impl.ClassInterceptorsCacheImpl;
import org.tynamo.security.services.impl.SecurityConfiguration;
import org.tynamo.security.services.impl.SecurityFilterChain;
import org.tynamo.security.services.impl.SecurityFilterChainFactoryImpl;
import org.tynamo.security.services.impl.SecurityServiceImpl;
import org.tynamo.security.shiro.SimplePrincipalSerializer;
import org.tynamo.shiro.extension.authz.aop.AopHelper;
import org.tynamo.shiro.extension.authz.aop.DefaultSecurityInterceptor;
import org.tynamo.shiro.extension.authz.aop.SecurityInterceptor;

/**
 * The main entry point for Security integration.
 *
 */
@Marker(Security.class)
public class SecurityModule
{
	private static final String PATH_PREFIX = "security";

	public static void bind(final ServiceBinder binder)
	{

		binder.bind(WebSecurityManager.class, TapestryRealmSecurityManager.class);
		// TYNAMO-155 It's not enough to identify ModularRealmAuthenticator by it's Authenticator interface only
		// because Shiro tests if the object is an instanceof LogoutAware to call logout handlers
		binder.bind(ModularRealmAuthenticator.class);
		binder.bind(SubjectFactory.class, DefaultWebSubjectFactory.class);
		binder.bind(HttpServletRequestFilter.class, SecurityConfiguration.class).withId("SecurityConfiguration");
		binder.bind(ClassInterceptorsCache.class, ClassInterceptorsCacheImpl.class);
		binder.bind(SecurityService.class, SecurityServiceImpl.class);
		binder.bind(SecurityFilterChainFactory.class, SecurityFilterChainFactoryImpl.class);
		binder.bind(ComponentRequestFilter.class, SecurityComponentRequestFilter.class);
//		binder.bind(ShiroExceptionHandler.class);
		binder.bind(LoginContextService.class, LoginContextServiceImpl.class);
		binder.bind(Serializer.class, SimplePrincipalSerializer.class);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static RememberMeManager buildRememberMeManager(Serializer serializer, Logger logger,
		@Symbol(SymbolConstants.HMAC_PASSPHRASE) String hmacPassphrase,
		@Symbol(SecuritySymbols.REMEMBERME_CIPHERKERY) String rememberMeCipherKey) throws UnsupportedEncodingException {
		CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
		// the default Shiro serializer produces obnoxiously long cookies
		rememberMeManager.setSerializer(serializer);

		// assume properly configured cipher is of the right width (divisable by 16)
		byte[] cipherKey = Base64.decode(rememberMeCipherKey);
		if (cipherKey.length <= 0) {
			if (hmacPassphrase.isEmpty()) {
				logger
					.error("Neither symbol '"
						+ SecuritySymbols.REMEMBERME_CIPHERKERY
						+ "' nor  '"
						+ SymbolConstants.HMAC_PASSPHRASE
						+ "' is set. Using a random value as the cipher key for encrypting rememberMe information. Cookies will be invalidated when the JVM is restarted");
				return rememberMeManager;
			}

			logger.warn("Symbol '" + SecuritySymbols.REMEMBERME_CIPHERKERY + "' is not set, using '"
				+ SymbolConstants.HMAC_PASSPHRASE
				+ "' as the cipher. Beware that changing the value will invalidate rememberMe cookies");
			if (hmacPassphrase.length() < 16) hmacPassphrase = hmacPassphrase + ("================".substring(hmacPassphrase.length()));
			cipherKey = hmacPassphrase.getBytes("UTF-8");
			if (cipherKey.length > 16) cipherKey = Arrays.copyOf(cipherKey, 16);
		}
		rememberMeManager.setCipherKey(cipherKey);
		return rememberMeManager;
	}

	public static void contributeFactoryDefaults(MappedConfiguration configuration)
	{
		configuration.add(SecuritySymbols.LOGIN_URL, "/" + PATH_PREFIX + "/login");
		configuration.add(SecuritySymbols.SUCCESS_URL, "/" + "${" + SymbolConstants.START_PAGE_NAME + "}");
		configuration.add(SecuritySymbols.UNAUTHORIZED_URL, "/" + PATH_PREFIX + "/unauthorized");
		configuration.add(SecuritySymbols.REDIRECT_TO_SAVED_URL, "true");
		configuration.add(SecuritySymbols.REMEMBERME_CIPHERKERY, "");
	}


	/**
	 * Create ClassInterceptorsCache through annotations on the class page,
	 * which then will use SecurityFilter.
	 * 

*/ public void contributeApplicationInitializer(OrderedConfiguration configuration, final ComponentClassResolver componentClassResolver, final ClassInterceptorsCache classInterceptorsCache) { configuration.add("SecurityApplicationInitializerFilter", new ApplicationInitializerFilter() { @Override public void initializeApplication(Context context, ApplicationInitializer initializer) { initializer.initializeApplication(context); for (String name : componentClassResolver.getPageNames()) { String className = componentClassResolver.resolvePageNameToClassName(name); Class clazz = ClassUtils.forName(className); while (clazz != null) { for (Class annotationClass : AopHelper.getAutorizationAnnotationClasses()) { Annotation classAnnotation = clazz.getAnnotation(annotationClass); if (classAnnotation != null) { //Add in the cache which then will be used in RequestFilter classInterceptorsCache.add(className, new DefaultSecurityInterceptor(classAnnotation)); } } clazz = clazz.getSuperclass(); } } } }); } public static void contributeComponentRequestHandler(OrderedConfiguration configuration, @Local ComponentRequestFilter filter) { configuration.add("SecurityFilter", filter, "before:*"); } @SuppressWarnings("rawtypes") @Contribute(Serializer.class) public static void addSafePrincipalTypes(Configuration configuration) { configuration.add(Long.class); configuration.add(String.class); configuration.add(Integer.class); } @Contribute(ComponentClassTransformWorker2.class) public static void addTransformWorkers(OrderedConfiguration configuration) { configuration.addInstance(ShiroAnnotationWorker.class.getSimpleName(), ShiroAnnotationWorker.class); } public static void contributeComponentClassResolver(Configuration configuration) { configuration.add(new LibraryMapping(PATH_PREFIX, "org.tynamo.security")); } /** * Secure all service methods that are marked with authorization annotations. *

* Restriction: Only service interfaces can be annotated. */ @Match("*") @Order("before:*") public static void adviseSecurityAssert(MethodAdviceReceiver receiver, final @Core Environment environment) { Class serviceInterface = receiver.getInterface(); for (Method method : serviceInterface.getMethods()) { List interceptors = AopHelper.createSecurityInterceptorsSeeingInterfaces(method, serviceInterface); for (final SecurityInterceptor interceptor : interceptors) { MethodAdvice advice = new MethodAdvice() { @Override public void advise(MethodInvocation invocation) { // Only (try to) intercept if subject is bound. // This is useful in case background or initializing operations // call service operations that are secure if (ThreadContext.getSubject() != null) { environment.push(MethodInvocation.class, invocation); try { interceptor.intercept(); } finally { environment.pop(MethodInvocation.class); } } invocation.proceed(); } }; receiver.adviseMethod(method, advice); } } } @SuppressWarnings("rawtypes") public void contributeRequestExceptionHandler(MappedConfiguration configuration) { configuration.add(ShiroException.class, SecurityExceptionHandlerAssistant.class); } public static void contributeHttpServletRequestHandler(OrderedConfiguration configuration, @InjectService("SecurityConfiguration") HttpServletRequestFilter securityConfiguration) { configuration.add("SecurityConfiguration", securityConfiguration, "after:StoreIntoGlobals", "before:IgnoredPaths"); } @Contribute(HttpServletRequestFilter.class) @Marker(Security.class) public static void defaultSecurity(Configuration configuration, SecurityFilterChainFactory factory) { configuration.add(factory.createChain("/modules.gz/**").add(factory.anon()).build()); configuration.add(factory.createChain("/modules/**").add(factory.anon()).build()); configuration.add(factory.createChain("/assets/**").add(factory.anon()).build()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy