Shiro权限管理框架(一)

Shiro权限管理框架

一. 简介

在这里插入图片描述
功能简介:
在这里插入图片描述在这里插入图片描述
Shiro架构
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述

二. 运行Shiro工程

创建Maven工程
pom文件如下:

	<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.3.2</version>
	</dependency>
	<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
 	</dependency>

建立日志配置文件

# For all other servers: Comment out the Log4J listener in web.xml to activate Log4J.
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
#begin
#for normal test,delete when online
log4j.logger.com.ibatis=DEBUG
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG

编写shiro配置文件

[users]
root=123456,admin
test=123456,role1,role2

[roles]
admin=*
role1=index.html,user.html
role2=user.html,menu.html
;anon 不进行任何验证
;authc 必须登录后才能访问
[urls]
/login.html=anon
/index.html=authc
/role.html=authc,roles[admin]
/menu/**=authc,roles[admin],perms[menu:*]

编写测试类(账号信息存在于配置文件中)

public class ShiroTest {
    
    

    public static void main(String[] args) {
    
    

        Factory<SecurityManager> factory= new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager=factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser=SecurityUtils.getSubject();
        System.out.println("是否登录"+currentUser.isAuthenticated());
        UsernamePasswordToken token=new UsernamePasswordToken("root","123456");
        try {
    
    
            currentUser.login(token);
        } catch (IncorrectCredentialsException e) {
    
    
            System.out.println("密码不正确");
        }catch (UnknownAccountException e) {
    
    
            System.out.println("用户名不存在");
        }
        System.out.println("登录");
        System.out.println("用户是否拥有admin角色"+currentUser.hasRole("admin"));
        System.out.println("用户能否访问url"+currentUser.isPermitted("index.html"));
        System.out.println("是否登录"+currentUser.isAuthenticated());

    }

}

编写测试类(账号信息存于数据库中,需要查库验证)
Pom文件中加入:

		 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.11.RELEASE</version>
        </dependency>

新的配置文件如下

[main]
dataSource=org.springframework.jdbc.datasource.DriverManagerDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://127.0.0.1:3306/test
dataSource.username=root
#如果数据库没有密码,就不要写这行
dataSource.password=123456
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

#是否检查权限
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.dataSource=$dataSource

#重写sql语句
#根据用户名查询出密码
jdbcRealm.authenticationQuery = select PASSWORD from SHIRO_USER where USER_NAME = ?
#根据用户名查询出角色
jdbcRealm.userRolesQuery = select ROLE_NAME from SHIRO_USER_ROLE where USER_NAME = ?
#根据角色名查询出权限
jdbcRealm.permissionsQuery = select PERM_NAME from SHIRO_ROLE_PERMISSION WHERE ROLE_NAME = ?
securityManager.realms=$jdbcRealm

为什么重写sql
查看负责与数据库进行交互的Realm,我们发现其中sql语句已经定义好了,而且此处sql的结构不能改变。例如select password from users where username = ?,无论字段名,表名怎么变,jdbcrealm都是当作从用户表里按照用户名查密码,即使你想按照ID查密码,它仍然当作用户名去查。
重写sql的意义在于,你可以按照自己的表名,字段名去定义这段sql,虽然不能更改sql的逻辑
我的数据库建表情况如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
表中数据如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
测试类如下:

public class ShiroTestFromJdbc {
    
    

    public static void main(String[] args) {
    
    

        Factory<SecurityManager> factory= new IniSecurityManagerFactory("classpath:shiro-mysql.ini");
        SecurityManager securityManager=factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser=SecurityUtils.getSubject();
        System.out.println("是否登录"+currentUser.isAuthenticated());
        UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
        try {
    
    
            currentUser.login(token);
        } catch (IncorrectCredentialsException e) {
    
    
            System.out.println("密码不正确");
        }catch (UnknownAccountException e) {
    
    
            System.out.println("用户名不存在");
        }
        System.out.println("登录");
        System.out.println("用户是否拥有admin角色"+currentUser.hasRole("admin"));
        System.out.println("用户能否访问url"+currentUser.isPermitted("index.html"));
        System.out.println("是否登录"+currentUser.isAuthenticated());
    }
}

接下来,我们将shiro与web工程进行整合,并自己编写realm(上述代码使用的是jdbcrealm)
pom文件中加入:

		<!--shiro web类库-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>

web.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <context-param>
        <param-name>shiroEnvironmentClass</param-name>
        <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
    </context-param>
    <!-- 此处用于加载shiro的配置文件-->
    <context-param>
        <param-name>shiroConfigLocations</param-name>
        <param-value>classpath:shiro-mysql.ini</param-value>
    </context-param>
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>ShiroFilter</filter-name>
        <url-pattern>*.html</url-pattern>
    </filter-mapping>
</web-app>

在配置文件中注入自定定义的realm

myRealmAR=com.hd.shiro.MyRealmAuthorizing
securityManager.realms=$myRealmAR
public class MyRealmAuthorizing extends AuthorizingRealm {
    
    

    @Override
    public String getName() {
    
    
        return "MyRealmAR";
    }

    /**
     * 用于授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
    
        System.out.println(principals.getPrimaryPrincipal());
        //根据主键去查询用户权限
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRole("admin");
        authorizationInfo.addRole("test");
        authorizationInfo.addStringPermission("index.html");
        return authorizationInfo;
    }

    /**
     *
     * @param token
     * @return 用于登录
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
        System.out.println(usertoken.getUsername());
        //根据用户名查询数据库,找到用户真正的密码
        if("admin".equals(usertoken.getUsername())){
    
    
            return new SimpleAuthenticationInfo("admin","123456",getName());
        }else{
    
    
            throw new UnknownAccountException(); //如果用户名错误
        }
    }
}

具体源码地址: https://gitee.com/hdyxk/shiro-framework-learning.
注意:
一.

 /=authc
            /login.html=anon
            /index.html = authc
            /role.html=authc,roles[admin]
            /menu/**=authc,roles[admin,test],perms[menu:*]

/menu/**同时需要角色和权限都满足才可以访问,但实际中我们希望只要角色和权限有一个满足即可,我们只需重写自己的权限过滤器即可

public class MyPermFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        String[] perms = (String[]) mappedValue;
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        for (String p : perms) {
            if (subject.isPermitted(p)) {
                session.setAttribute("Allowed", true);
                return true;
            }
        }
        return false;
    }
}
public class MyRoleFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        String[] roles = (String[]) mappedValue;

        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        if (session.getAttribute("Allowed") != null) {
            return true;
        }
        for (String role : roles) {
            if (subject.hasRole(role)) {
                return true;
            }
        }
        return false;
    }
}

在shirofilter的bean中加入:

</property>
        <property name="filters">
            <map>
                <entry key="roles">
                    <bean class="filter.MyRoleFilter"/>
                </entry>
                <entry key="perms">
                    <bean class="filter.MyPermFilter"/>
                </entry>
            </map>
        </property>

二.

 @RequiresPermissions("menu:edit")
    @RequestMapping("/menu/list.html")
    public String list() {
        return "menu";
    }

使用注解可是进行细粒度的权限控制,比如访问呢list.html必须拥有menu.edit权限。
三. 如果权限认真失败,我们可以设置增强,对异常封装,返回指定信息

@ControllerAdvice
public class AuthExceptionHandler {
    
    
    @ExceptionHandler({
    
    UnauthorizedException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {
    
    
        ModelAndView mv = new ModelAndView();
        mv.addObject("exception", e.getMessage());
        mv.setViewName("error");
        return mv;
    }
}

四. 在shiro过滤器中,匹配规则在实际中是存在数据库中的。为此我们需要重写ShiroFilterFactoryBean

<value>
            /=authc
            /login.html=anon
            /index.html = authc
            /role.html=authc,roles[admin]
            /menu/**=authc,roles[admin,test],perms[menu:*]
        </value>

但是只是重写设置匹配规则的方法即可

public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean {
    
    
    @Override
    public void setFilterChainDefinitions(String definitions) {
    
    
        Ini ini = new Ini();
        ini.load(definitions);
        Ini.Section section = ini.getSection("url s");
        if (CollectionUtils.isEmpty(section)) {
    
    
            section = ini.getSection("");
        }
        section.put("/menu/**","roles[admin]");
        this.setFilterChainDefinitionMap(section);
    }
}
 /menu/**=authc,roles[admin,test],perms[menu:*]等价于section.put("/menu/**","roles[admin]");

猜你喜欢

转载自blog.csdn.net/weixin_44726976/article/details/108747683