频道栏目
首页 > 资讯 > 其他 > 正文

架构之路之spring+shiro的集成

17-02-22        来源:[db:作者]  
收藏   我要投稿

1.前言

1.1 shiro介绍

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

1.2使用api

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
 

2.集成项目

2.1 依赖

 


        
            org.apache.shiro
            shiro-core
            1.2.3
        
        
            org.apache.shiro
            shiro-web
            1.2.3
        
        
            org.apache.shiro
            shiro-spring
            1.2.3
        

 

2.2 shiro-context.xml

 




    
    
        
        
        
        
    

    
    

    
    
        
        
        
        
    

    
    
        
        
        
        
        
        
        
        
        
        
            
                
                /public/** = anon
                
                /static/**=anon
                
                /user/edit=perms[user:edit]
                
                
                
                /** = authc
                
            
        
    

    
    
    
    

    
    
    

    
    
        
    

    
    
    
        
    

 

2.3 导入spring-context.xml和springmvc-context.xml中

 


这边对之前的配置要做一些修改,涉及到spring父子容器的查看范围的问题,参考下一篇文章

点击打开链接

 

修改的spring-context.xml:

 

 
        
        
    

修改的springmvc-context.xml:

 

 


    
        
    

 

2.4 web.xml

 


        shiroFilter
        org.springframework.web.filter.DelegatingFilterProxy
    
    
        shiroFilter
        /*
    

这个shiroFilter与shiro-context.xml中的对应。

 

2.5 自定义AuthRealm

 

package com.tl.skyLine.shiro;

import com.tl.skyLine.model.User;
import com.tl.skyLine.repository.PermissionDao;
import com.tl.skyLine.repository.RoleDao;
import com.tl.skyLine.repository.UserDao;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.stream.Collectors;

/**
 * Created by tl on 17/2/20.
 */
@Component
public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private PermissionDao permissionDao;

    @Autowired
    private UserDao userDao;

    /**
     * 用来为当前登陆成功的用户授予权限和角色(已经登陆成功了)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        //获取用户名
        //String username = (String) principals.getPrimaryPrincipal();
        //获取当前用户
        User user = (User) principals.fromRealm(getName()).iterator().next();
        //得到权限字符串
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        info.addRoles(roleDao.getRoles(user.getId())
                .stream().map(role -> role.getName()).collect(Collectors.toList()));
        info.addStringPermissions(permissionDao.getPermissionByUser(user.getId())
                .stream().map(permission -> permission.getName()).collect(Collectors.toList()));
        return info;
    }

    /**
     * 用来验证当前登录的用户,获取认证信息
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authcToken) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) authcToken;

        User user = userDao.findOneByUsername(upToken.getUsername());
        if (user == null) {
            return null;
        } else {
            AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
            return info;
        }
    }

}

在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理。 因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。 可以说,Realm是专用于安全框架的DAO.

 

shiro登陆成功,再次访问url的时候会请求doGetAuthorizationInfo时候会将登陆用户对应的角色,权限全部查到,然后set到SimpleAuthorizationInfo实例对象info中,保存的分别是role和permission对象的name字段,

这个name子段跟shiro-context.xml中的shiroFilterbean对象的filterChainDefinitions属性对应:

2.6 角色权限类的结构

我这边是用mongodb数据库,没有外键关联约束,就是基本的用户,角色,权限,然后外加两个中间表,我就不一一贴出了。

2.7 repository

由于非关系数据库的约束性,这边做表关联查询没有关系型数据库那么简单,这个对整体框架没有影响,我把这个代码贴一下:

RoleDaoImpl:

 

package com.tl.skyLine.repository.impl;

import com.tl.skyLine.model.Role;
import com.tl.skyLine.model.UserRole;
import com.tl.skyLine.repository.RoleDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * RoleDaoImpl
 * Created by tl on 17/2/13.
 */
@Component("roleDao")
public class RoleDaoImpl implements RoleDao {

    @Autowired
    private MongoTemplate mongoTemplate;


    @Override
    public Role findOne(String roleId) {
        return this.mongoTemplate.findOne(new Query().addCriteria(Criteria.where("id").is(roleId)), Role.class);
    }

    @Override
    public List getRoles(String userId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId));
        List userRoles = this.mongoTemplate.find(query, UserRole.class);
        List roles = new ArrayList();
        userRoles.stream().forEach(userRole -> {
            roles.add(this.findOne(userRole.getRoleId()));
        });
        return roles;
    }

    @Override
    public void store(Role role) {
        this.mongoTemplate.save(role);
    }

    @Override
    public void store(UserRole userRole) {
        this.mongoTemplate.save(userRole);
    }

    @Override
    public Role findOneByName(String name) {
        return this.mongoTemplate.findOne(
                new Query().addCriteria(Criteria.where("name").is(name))
                , Role.class);
    }
}

PermissionImpl:

 

 

package com.tl.skyLine.repository.impl;

import com.tl.skyLine.model.Permission;
import com.tl.skyLine.model.RolePermission;
import com.tl.skyLine.repository.PermissionDao;
import com.tl.skyLine.repository.RoleDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * PermissionDaoImpl
 * Created by tl on 17/2/13.
 */
@Component("permissionDao")
public class PermissionDaoImpl implements PermissionDao {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private RoleDao roleDao;


    @Override
    public Permission findOne(String permissionId) {
        return this.mongoTemplate.findOne(new Query().addCriteria(Criteria.where("id").is(permissionId)), Permission.class);
    }


    @Override
    public List getPermissionByRole(String roleId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("roleId").is(roleId));
        List rolePermissions = this.mongoTemplate.find(query, RolePermission.class);
        List permissions = new ArrayList();
        rolePermissions.stream().forEach(rolePermission -> {
            permissions.add(this.findOne(rolePermission.getPermissionId()));
        });
        return permissions;
    }

    @Override
    public List getPermissionByUser(String userId) {
        List permissions = new ArrayList();
        roleDao.getRoles(userId).stream().forEach(role -> {
            permissions.addAll(this.getPermissionByRole(role.getId()));
        });
        return permissions;
    }

    @Override
    public void store(Permission permission) {
        this.mongoTemplate.save(permission);
    }

    @Override
    public void store(RolePermission rolePermission) {
        this.mongoTemplate.save(rolePermission);
    }

    @Override
    public Permission findOneByName(String name) {
        return this.mongoTemplate.findOne(
                new Query().addCriteria(Criteria.where("name").is(name))
                , Permission.class);
    }
}

 

2.8 单元测试增加测试数据

 

    /**
     * 关联用户角色权限
     */
    @Test
    public void testShiro() {
        Role role = new Role();
        role.setName("admin");
        role.setDescription("管理员权限");
        roleDao.store(role);

        UserRole userRole = new UserRole();
        userRole.setRoleId(role.getId());
        userRole.setUserId(userDao.findOneByUsername("admin").getId());

        roleDao.store(userRole);

        Permission permission = new Permission();
        permission.setName("edit");
        permission.setDescription("编辑权限");
        permissionDao.store(permission);

        RolePermission rolePermission = new RolePermission();
        rolePermission.setRoleId(role.getId());
        rolePermission.setPermissionId(permission.getId());

        permissionDao.store(rolePermission);
    }

 

2.9 controller类和jsp

PublicController:

 

/**
 * PublicController
 * Created by tl on 17/2/13.
 */
@Controller
@RequestMapping("/public")
public class PublicController {

    @Autowired
    private UserDao userDao;


    //用户登录
    @RequestMapping("/login")
    public String login(User user, HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        userDao.findAll();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        try {
            subject.login(token);//会跳到我们自定义的realm中
            request.getSession().setAttribute("user", user);
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            request.getSession().setAttribute("user", user);
            request.setAttribute("error", "用户名或密码错误!");
            return "login";
        }
    }

    @RequestMapping("/logout")
    public String logout(HttpServletRequest request) {
        request.getSession().invalidate();
        return "index";
    }

}

login.jsp

 

 

<%@ page language="java" contentType="text/html; charset=GB2312" %>
<% String path = request.getContextPath(); // 获得项目完全路径(假设你的项目叫skyLine,那么获得到的地址就是http://localhost:8080/skyLine/): String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %>
用户名:
密码:
${error}

success.jsp

 

 

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

<%@ page language="java" contentType="text/html; charset=GB2312" %>

登陆成功!欢迎你${user.username}

进入用户list页面

你有权限看到此处!


上面的注解,下节再讲

点击打开链接

3.开始测试shiro权限

首先项目启动之后,不再运行web.xml这行代码,而是直接被shiroFilter过滤,执行shiro-context.xml配置文件中的,进入登录页面,然后输入登录信息,进入publicController中的login方法,执行到subject.login(token);时会跳转到我们自定义的realm中进行用户名和密码校验,成功跳转success.jsp,失败重新返回login.jsp页面,同时显示报错信息!

下面针对shiro-contex.xml中的url设置,访问http://localhost:8080/user/edit,提示访问成功,如果将配置文件的/user/edit=perms[user:edit]中的use:edit改成use:edit,重新启动,再次访问则会报错,提示没有访问权限

 

但是,上面的配置有一个局限性,就是每次在配置文件里面加url权限,会很麻烦,每次都要重启,而且shiro权限是细粒化的这种加配置的方法会产生很多代码,是有意下文我们将介绍通过注解在controller和jsp中如何实现权限细粒化!

 

相关TAG标签
上一篇:spring容器启动会默认执行的注解
下一篇:深入理解IOC模式及Unity框架
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站