001/*
002 * Copyright 2012 Atteo.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.atteo.moonshine.shiro;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.List;
021
022import javax.inject.Inject;
023import javax.servlet.FilterChain;
024import javax.servlet.ServletRequest;
025import javax.servlet.ServletResponse;
026import javax.xml.bind.annotation.XmlElement;
027import javax.xml.bind.annotation.XmlElementRef;
028import javax.xml.bind.annotation.XmlElementWrapper;
029import javax.xml.bind.annotation.XmlRootElement;
030
031import org.apache.shiro.SecurityUtils;
032import org.apache.shiro.authz.annotation.RequiresGuest;
033import org.apache.shiro.authz.annotation.RequiresPermissions;
034import org.apache.shiro.authz.annotation.RequiresUser;
035import org.apache.shiro.guice.ShiroModule;
036import org.apache.shiro.guice.aop.ShiroAopModule;
037import org.apache.shiro.guice.web.GuiceShiroFilter;
038import org.apache.shiro.guice.web.ShiroWebModule;
039import org.apache.shiro.mgt.SecurityManager;
040import org.apache.shiro.realm.Realm;
041import org.apache.shiro.session.mgt.SessionManager;
042import org.apache.shiro.util.ThreadContext;
043import org.apache.shiro.web.filter.mgt.FilterChainResolver;
044import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
045import org.apache.shiro.web.mgt.WebSecurityManager;
046import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
047import org.atteo.config.XmlDefaultValue;
048import org.atteo.moonshine.TopLevelService;
049import org.atteo.moonshine.services.Service;
050
051import com.google.inject.Key;
052import com.google.inject.Module;
053import com.google.inject.PrivateModule;
054import com.google.inject.binder.AnnotatedBindingBuilder;
055import com.google.inject.multibindings.Multibinder;
056import com.google.inject.name.Names;
057
058/**
059 * ShiroService service.
060 *
061 * <p>
062 * Binds {@link SecurityManager}.
063 * </p>
064 */
065@XmlRootElement(name = "shiro")
066public class ShiroService extends TopLevelService {
067
068    /**
069     * Enables Shiro AOP functionality.
070     *
071     * <p>
072     * With Shiro AOP you can use annotations for permission checking:
073     * {@link RequiresPermissions}, {@link RequiresUser}, {@link RequiresGuest}, etc.
074     * </p>
075     */
076    @XmlElement
077    @XmlDefaultValue("true")
078    private Boolean aop;
079
080    @XmlElementWrapper(name = "realms")
081    @XmlElementRef
082    private List<RealmService> realms = new ArrayList<>();
083
084    /**
085     * URL prefix to filter through ShiroService.
086     */
087    @XmlElement
088    private String prefix = "/*";
089
090    @Override
091    public Iterable<? extends Service> getSubServices() {
092        return realms;
093    }
094
095    @Override
096    public Module configure() {
097        return new PrivateModule() {
098            @Override
099            protected void configure() {
100                install(new ShiroModule() {
101                    @Override
102                    protected void configureShiro() {
103                        Multibinder<Realm> setBinder = Multibinder.newSetBinder(binder(), Realm.class);
104                        for (RealmService realm : realms) {
105                            if (realm.getId() == null) {
106                                setBinder.addBinding().to(Realm.class);
107                            } else {
108                                setBinder.addBinding().to(Key.get(Realm.class, Names.named(realm.getId())));
109                            }
110
111                        }
112
113                        try {
114                            // Guice will initialize manager with list of realms
115                            bind(WebSecurityManager.class).toConstructor(
116                                    DefaultWebSecurityManager.class.getConstructor(Collection.class))
117                                    .asEagerSingleton();
118                        } catch (NoSuchMethodException e) {
119                            addError(e);
120                        }
121                        expose(WebSecurityManager.class);
122                    }
123
124                    @Override
125                    protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
126                        // make configurable
127                        bind.to(DefaultWebSessionManager.class).asEagerSingleton();
128                    }
129                });
130                FilterChainResolver filterChainResolver = new FilterChainResolver() {
131                    @Override
132                    public FilterChain getChain(ServletRequest request, ServletResponse response,
133                            FilterChain chain) {
134                        return null;
135                    }
136                };
137                bind(FilterChainResolver.class).toInstance(filterChainResolver);
138
139                bind(GuiceShiroFilter.class).asEagerSingleton();
140
141                install(ShiroWebModule.guiceFilterModule(prefix));
142                if (aop) {
143                    install(new ShiroAopModule());
144                }
145
146                expose(SecurityManager.class);
147                expose(WebSecurityManager.class);
148            }
149        };
150    }
151
152    @Inject
153    private SecurityManager securityManager;
154
155    @Override
156    public void start() {
157        SecurityUtils.setSecurityManager(securityManager);
158    }
159
160    @Override
161    public void close() {
162        SecurityUtils.setSecurityManager(null);
163
164        ThreadContext.unbindSecurityManager();
165        ThreadContext.unbindSubject();
166    }
167}