Shiro(4) 与Web的集成

将Shiro集成到Web应用中,只需要在web.xml中配置ContextListener和Filter.
对于Shiro来说,集成到Web应用中,需要解决以下问题:

  • SecurityManager 等Shiro用到的组件与ServletContext的绑定。
  • 将请求的URI与权限对应。

为了方便,我们首先使用基于*.ini配置文件的方式来进行集成。

web.xml

<filter>
	<filter-name>shiroFilter</filter-name>
	<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
	<init-param>
		<param-name>configPath</param-name>
		<param-value>classpath:shiro.ini</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>shiroFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>


<servlet>
	<servlet-name>LoginServlet</servlet-name>
	<servlet-class>hello.shiro.demo3.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>LoginServlet</servlet-name>
	<url-pattern>/login</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>UserServlet</servlet-name>
	<servlet-class>hello.shiro.demo3.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>UserServlet</servlet-name>
	<url-pattern>/user</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>AdminServlet</servlet-name>
	<servlet-class>hello.shiro.demo3.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>AdminServlet</servlet-name>
	<url-pattern>/admin</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>AdminCreateServlet</servlet-name>
	<servlet-class>hello.shiro.demo3.AdminCreateServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>AdminCreateServlet</servlet-name>
	<url-pattern>/admin/create</url-pattern>
</servlet-mapping>

<listener>
	<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

shiro.ini

[main]
#定义身份认证失败后的请求url映射
authc.loginUrl=/login

#定义用户和角色
[users]
user=123,user
admin=123,admin

#定义不同角色的权限
[roles]
user=user:*
admin=admin:*

[urls]
#/login,不需要权限(anon)
/login=anon 
#/user,需要身份认证(authc)
/user=authc
#/admin,需要角色认证(admin)
/admin=roles[admin]
#/admin/create,需要权限认证(admin:create)
/admin/create=perms[admin:create]

LoginServlet

public class LoginServlet extends HttpServlet{

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("login");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            currentUser.login(token);
            System.out.println("认证成功");
            request.getSession().setAttribute("username", username);
            if(currentUser.hasRole("user")){
            	response.sendRedirect("/user");
            }else if(currentUser.hasRole("admin")){
            	response.sendRedirect("/admin");
            }
        	
        } catch (AuthenticationException e) {
            e.printStackTrace();
            System.out.println("认证失败");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
	}
}

UserServlet

public class UserServlet extends HttpServlet {

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("message", "欢迎您:"+request.getSession().getAttribute("username"));
        request.getRequestDispatcher("/success.jsp").forward(request, response);
    }
}  

AdminServlet

public class AdminServlet extends HttpServlet {

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("message", "欢迎您,管理员:"+request.getSession().getAttribute("username"));
        request.getRequestDispatcher("/success.jsp").forward(request, response);
    }
}  

AdminCreateServlet

public class AdminCreateServlet extends HttpServlet {

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("message", "创建管理员");
        request.getRequestDispatcher("/success.jsp").forward(request, response);
    }
}

login.jsp

<body>
<form action="${request.contextPath }/login" method="post">
    username:<input type="text" name="username"/><br>
    password:<input type="password" name="password"/><br>
    <input type="submit" value="登陆">
</form>
</body>

success.jsp

<body>
     ${message }
</body>

接下来运行并访问 /user,/admin, /admin/create ,发现跳转到 /login。
使用user账户登陆后,访问/user正常显示, 访问 /admin, /admin/create报401错误,没有权限访问
使用admin账户登陆后,所有请求都可以正常访问

EnvironmentLoaderListener

用于初始化Shiro,用于创建并初始化 MutableWebEnvironment

    ContextListener的容器初始化
    public void contextInitialized(ServletContextEvent sce) {
        initEnvironment(sce.getServletContext());
    }
    
    public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
        //......
        
        long startTime = System.currentTimeMillis();

        try {
            创建一个 WebEnvironment ,并绑定到ServletContext中
            WebEnvironment environment = createEnvironment(servletContext);
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment);
            
            return environment;
        }
        //......
    }
    
    protected WebEnvironment createEnvironment(ServletContext sc) {
        //获取在web.xml中通过 shiroEnvironmentClass 参数指定的 WebEnviroment的实现类,
        //如果没有指定,默认使用 基于 *.ini配置文件的 IniWebEnvironment
        Class<?> clazz = determineWebEnvironmentClass(sc);
        //实现类必须实现了 MutableWebEnvironment 接口,否则抛出异常
        if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) {
            throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() +
                    "] is not of required type [" + MutableWebEnvironment.class.getName() + "]");
        }
        
        //通过 shiroConfigLocations 参数在web.xml配置的配置文件路径
        String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
        //配置文件路径为空,即没有指定配置文件
        boolean configSpecified = StringUtils.hasText(configLocations);
        
        //如果指定的 WebEnvironment类型实现了ResourceConfigurable 接口,则必须指定配置文件路径
        if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) {
            String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " +
                    ResourceConfigurable.class.getName() + "interface.  This is required to accept any " +
                    "configured " + CONFIG_LOCATIONS_PARAM + "value(s).";
            throw new ConfigurationException(msg);
        }

        //创建具体的 “环境” 实例
        MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz);
        environment.setServletContext(sc);

        if (configSpecified && (environment instanceof ResourceConfigurable)) {
            ((ResourceConfigurable) environment).setConfigLocations(configLocations);
        }

        customizeEnvironment(environment);
        
        //初始化 “环境”
        LifecycleUtils.init(environment);

        return environment;
    }

MutableWebEnvironment

Shiro的运行环境,在Web应用启动时,EnvironmentLoaderListener会创建并初始化 EnvironmentLoaderListener
在上面的案例中我们使用的是基于*.ini配置文件的方式, 因此会使用默认的 IniWebEnvironment 类
我们看看IniWebEnvironment的初始化方法:

public void init() {
        //...... 省略,上面一大段都是根据配置文件创建 Ini 实例的
        configure();
    }
    
    protected void configure() {
        //清空已经存储的对象,
        this.objects.clear();

        //通过WebIniSecurityManagerFactory创建一个 DefaultWebSecurityManager
        WebSecurityManager securityManager = createWebSecurityManager();
        setWebSecurityManager(securityManager);
        //通过IniFilterChainResolverFactory创建一个PathMatchingFilterChainResolver
        //在此过程中会创建 并初始化 FilterChainManager
        FilterChainResolver resolver = createFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }

接下来查看 IniFilterChainResolverFactory是如何创建 FilterChainManager的

    protected FilterChainResolver createInstance(Ini ini) {
        //创建 FilterChainResolver 与 FilterChainManager
        FilterChainResolver filterChainResolver = createDefaultInstance();
        
        if (filterChainResolver instanceof PathMatchingFilterChainResolver) {
            PathMatchingFilterChainResolver resolver = (PathMatchingFilterChainResolver) filterChainResolver;
            FilterChainManager manager = resolver.getFilterChainManager();
            //构建 FilterChainManager
            buildChains(manager, ini);
        }
        return filterChainResolver;
    }
    
    protected FilterChainResolver createDefaultInstance() {
        FilterConfig filterConfig = getFilterConfig();
        //创建 PathMatchingFilterChainResolver 时会创建 DefaultFilterChainManager
        if (filterConfig != null) {
            return new PathMatchingFilterChainResolver(filterConfig);
        } else {
            return new PathMatchingFilterChainResolver();
        }
    }
    
    //
    protected void buildChains(FilterChainManager manager, Ini ini) {
        //配置文件中的配置
        Ini.Section section = ini.getSection(FILTERS);

        Map<String, Object> defaults = new LinkedHashMap<String, Object>();
        //开始合并各处的Filter
        
        //FilterChainManager 已经有的默认的Filter , 具体可查看 DefaultFilter
        Map<String, Filter> defaultFilters = manager.getFilters();
        
        if (!CollectionUtils.isEmpty(defaultFilters)) {
            defaults.putAll(defaultFilters);
        }
        //创建工程时指定的一些对象
        if (!CollectionUtils.isEmpty(this.defaultBeans)) {
            defaults.putAll(this.defaultBeans);
        }
        //将defaults中的非Filter对象去除,并添加 ini中配置的Filter
        Map<String, Filter> filters = getFilters(section, defaults);
        
        registerFilters(filters, manager);
        
        //生成各url对应的过滤器链
        section = ini.getSection(URLS);
        createChains(section, manager);
    }
    
    //将Filter绑定到 FilterChainManager
    protected void registerFilters(Map<String, Filter> filters, FilterChainManager manager) {
        if (!CollectionUtils.isEmpty(filters)) {
            boolean init = getFilterConfig() != null;
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                manager.addFilter(name, filter, init);
            }
        }
    }

    //根据url生成对应的NamedFilterList
    protected void createChains(Map<String, String> urls, FilterChainManager manager) {
        if (CollectionUtils.isEmpty(urls)) {
            if (log.isDebugEnabled()) {
                log.debug("No urls to process.");
            }
            return;
        }

        if (log.isTraceEnabled()) {
            log.trace("Before url processing.");
        }

        for (Map.Entry<String, String> entry : urls.entrySet()) {
            String path = entry.getKey();
            String value = entry.getValue();
            manager.createChain(path, value);
        }
    }

ShiroFilter

ShiroFilter是Shiro对请求进行拦截的入口,在这个过滤器中,

    //此方法用于权限验证
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        //根据请求 从 FilterChainResolver 中获取对应的 FilterChain。
        //如果获取不到,返回原来的 origChain
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
    
    //根据请求 从 FilterChainResolver 中获取对应的 FilterChain。如果获取不到,返回原来的 origChain
     protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            return origChain;
        }
        
        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            chain = resolved;
        }
        return chain;
    }

FilterChainResolver

过滤器链解析器,用于根据请求解析出对应的过滤器链。
它只有一个实现类:PathMatchingFilterChainResolver,用于根据URI从FilterChainManager中获取对应的过滤器链

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    FilterChainManager filterChainManager = getFilterChainManager();
    if (!filterChainManager.hasChains()) {
        return null;
    }

    String requestURI = getPathWithinApplication(request);

    //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
    //as the chain name for the FilterChainManager's requirements
    for (String pathPattern : filterChainManager.getChainNames()) {

        // If the path does match, then pass on to the subclass implementation for specific checks:
        if (pathMatches(pathPattern, requestURI)) {
            if (log.isTraceEnabled()) {
                log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                        "Utilizing corresponding filter chain...");
            }
            return filterChainManager.proxy(originalChain, pathPattern);
        }
    }

    return null;
}

FilterChainManager

过滤器链管理器,用于管理请求对应的过滤器链
它主要有两个方法:

//获取请求对应的过滤器集合
public NamedFilterList getChain(String chainName) {
        return this.filterChains.get(chainName);
}
//对过滤器链进行封装,返回Shiro自定义的过滤器链 ProxiedFilterChain
public FilterChain proxy(FilterChain original, String chainName) {
    NamedFilterList configured = getChain(chainName);
    if (configured == null) {
        String msg = "There is no configured chain under the name/key [" + chainName + "].";
        throw new IllegalArgumentException(msg);
    }
    return configured.proxy(original);
}

ProxiedFilterChain

Shiro封装的过滤器链,在请求到达时, ShiroFilter会将Servlet容器的FilterChain封装称 ProxiedFilterChain

查看ProxiedFilterChain源码可以看到 请求将 依次通过Shiro提供的各种Filter,然后再交给ShiroFilter后面的Filter进行处理

public class ProxiedFilterChain implements FilterChain {

    private FilterChain orig; //ShiroFilter中原来的过滤器链
    private List<Filter> filters; //Shiro根据请求从 FilterChainManager 中获取的过滤器集合
    private int index = 0;

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }
}

最后列出Shiro内置过滤器:

anon(AnonymousFilter.class)

匿名认证,永远认证通过

authc(FormAuthenticationFilter.class)

身份认证,需要登陆

authcBasic(BasicHttpAuthenticationFilter.class)

Basic HTTP身份验证拦截器

logout(LogoutFilter.class)

退出拦截器,退出成功后重定向到redirectUrl

noSessionCreation(NoSessionCreationFilter.class)

禁止创建Session.
如果一个Subject在这个过滤器被调用时还没有Session,调用Subject.getsession()和Subject.getsession(true)将抛出一个异常。
但是,如果这个主题在调用这个过滤器之前已经有了一个关联的Session,那么这个过滤器就没有效果。

perms(PermissionsAuthorizationFilter.class)

权限授权拦截器,验证用户是否拥有所有权限

port(PortFilter.class)
端口拦截器,可以指定请求通过的端口;
如果用户访问该页面不是通过 port属性指定的端口,将自动将请求端口改为指定端口,并重定向到指定端口

rest(HttpMethodPermissionFilter.class)

rest拦截器,自动根据请求构建权限字符串构建权限字符串;
权限规则:(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)
例如:“/users=rest[user]”,
会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll)

roles(RolesAuthorizationFilter.class)

角色认证
如果拥有指定角色,则通过认证。否则拒绝访问

ssl(SslFilter.class)

SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样

user(UserFilter.class)

允许已知的用户访问资源,包括已经登陆的或者通过“记住我”记住的用户
如果没有,则跳转到loginUrl

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值