Shiro的学习Helloworld

Shiro的学习Helloworld

8月 18, 2017 框架相关, 权限管理

  1. Apache Shiro是Java的一个安全框架
    1.1. Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等
  2. Shiro基本功能
  3. 1.身份认证
  4. 2.权限认证
    4.1. 核心要素:(资源,)权限,角色,用户
  5. 3.集成web进行测试
    5.0.1. 1.新建一个maven的web项目
    5.0.1.1. 1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址…(loginUrl是authc的一个属性)
    5.0.1.2. 2.roles.unauthorizeUrl配置角色认证不通过跳转的地址…(noAuth.jsp页面目前只有一行字)
    5.0.1.3. 3.perms.unauthorizeUrl配置权限认证不通过跳转的地址
    5.0.1.4. 4.[users]下配置用户身份信息以及用户角色
    5.0.1.5. 5.[roles]下配置角色以及角色的控制权限
    5.0.1.6. 6.[urls]下配置访问地址所需的权限, 其中值为”anon过滤器”表示地址不需要登录即可访问; “authc过滤器”表示地址登录才能访问
    5.0.1.7. 7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围
    5.0.1.8. 8.值为 perms[“student:create”] 表示 必须有权限为”student:create”的用户才能范围
    5.0.1.9. 9.多个过滤器用”,”隔开 而且相互为”且”的关系(必须同时满足才能访问)
    5.0.1.10. 10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef…..)
    5.0.1.11. 11.地址可以使用表示匹配任意个任意字符(eg: /home=authc 表示可过滤 /home123; /homeef…..)
    5.0.1.12. 12.地址可以使用表示匹配多路径(eg: /home/=authc 表示可过滤 /home/abc; /home/aaa/bbb…..)
    5.0.2. 2.编写sevlet代码
    5.0.3. 3.启动项目
    5.0.3.1. 测试身份认证
    5.0.3.2. 测试角色认证
    5.0.3.3. 测试权限认证

  6. 6.0.1. 自定义Realm
    6.0.1.1. 测试开发步骤
    6.0.1.2. 1.myRealm指向自定义Realm的位置
    6.0.1.3. 2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用”,”隔开
    Apache Shiro是Java的一个安全框架
    Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等
    图片说明
    Shiro基本功能
    Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

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

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

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

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

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

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

Testing:提供测试支持;

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

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


1.身份认证
配置文件:
**
[users]
zj2626=123456
ay2626=456789
**

package com.em;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

public class Hello {

public static void main(String args[]) {
    //初始化SecurityManager工厂      读取配置文件中的用户名密码
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    //获取SecurityManager实例
    SecurityManager manager = factory.getInstance();
    //把SecurityManager实例绑定到SecurityUtils
    SecurityUtils.setSecurityManager(manager);
    //得到当前执行的用户
    Subject subject = SecurityUtils.getSubject();
    //创建token令牌, 用户/密码
    UsernamePasswordToken token = new UsernamePasswordToken("zj2626", "123456");

    try {
        //身份认证 登录
        subject.login(token);

        System.out.println("登录成功");
    } catch (Exception e) {
        System.out.println("登录失败");
        e.printStackTrace();
    }

    subject.logout();
}

/*
    Subject: 认证主体
    Principals:  身份: 用户名
    Credentials: 凭证: 密码

    Realm: 域
        1.jdbc realm | 2.jndi realm | 3.text realm

 */

}
从数据库中读取用户名密码 实现登录

1.配置文件: jdbc_realm.ini (代码只需把读取的文件改成此文件即可测试使用)

[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test
dataSource.user=root
dataSource.password=123456
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm
2.权限认证
核心要素:(资源,)权限,角色,用户
用户–(分配)–>角色–(拥有)–>权限–(控制)—->资源

用户代表访问系统的用户,即subject。
三种授权方式

  1. 编程式授权
    1.1. 基于角色的访问控制
    1.2. 基于权限的访问控制
  2. 注解式授权
    3.JSP标签授权
    ####步骤1: 封装一个工具类

public class ShiroUtils {
public static Subject login(String conf, String username, String passowrd) {
//初始化SecurityManager工厂 conf是配置文件名称
Factory factory = new IniSecurityManagerFactory(“classpath:” + conf + “.ini”);
//获取SecurityManager实例
SecurityManager manager = factory.getInstance();
//把SecurityManager实例绑定到SecurityUtils
SecurityUtils.setSecurityManager(manager);
//得到当前执行的用户
Subject subject = SecurityUtils.getSubject();
//创建token令牌, 用户/密码
UsernamePasswordToken token = new UsernamePasswordToken(username, passowrd);

    try {
        //身份认证 登录
        subject.login(token);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return subject;
}

}

####步骤2: 测试多种访问控制

/*
基于角色的访问控制方式1

配置文件:shiro_role.ini
******************************
[users]
zj2626=123456,role1,role2
py2626=123456,role1
ay2626=456789,role3
******************************
 */
@Test
public void testHas() {
    Subject sub = ShiroUtils.login("shiro_config/shiro_role", "zj2626", "123456");

    //判断有没有权限 返回布尔 表示验证的成功与否
    boolean bool = sub.hasRole("role1");
    if (bool) {
        System.out.println("HAS");
    }

    //判断有没有权限,一次多个分别判断 返回布尔数组
    boolean[] booleans = sub.hasRoles(Arrays.asList("role1", "role2", "role3"));
    int i = 0;
    while (booleans[i]) {
        i++;

        if (booleans.length <= i) {
            break;
        }
    }

    //所有的角色都有才返回true
    System.out.println(sub.hasAllRoles(Arrays.asList("role1", "role2", "role3")));

    //判断有没有权限 没有则抛异常
    sub.checkRole("role1");
    sub.checkRole("role3");

    //判断多个权限 有一个没有就抛异常 (2种参数形式)
    sub.checkRoles(Arrays.asList("role1", "role2", "role3"));
    sub.checkRoles("role1", "role2", "role3");

    //退出登陆
    sub.logout();
}

/*
基于权限的访问控制方式(过程同上)

配置文件:shiro_permision.ini
******************************
[users]
zj2626=123456,role1,role2
py2626=123456,role1
ay2626=456789,role3
[roles]
role1=user:select
role2=user:add,user:update,user:delete
******************************
 */
@Test
public void testPermition() {
    Subject sub = ShiroUtils.login("shiro_config/shiro_permision", "py2626", "123456");

    System.out.println("用户是否有权限 user:select:" + sub.isPermitted("user:select"));    //true
    System.out.println("用户是否有权限 user:update:" + sub.isPermitted("user:update"));    //false

    boolean[] booleans = sub.isPermitted("user:add", "user:select");
    System.out.println(booleans[0] + "____" + booleans[1]);

    System.out.println(sub.isPermittedAll("user:add", "user:select"));

    //没有会抛出异常
    sub.checkPermission("user:select");
    sub.checkPermissions("user:select", "user:update");

    sub.logout();
}

3.集成web进行测试
1.新建一个maven的web项目

项目目录文件

web.xml 配置shiro的必须的配置: 监听器,过滤器

<?xml version=”1.0” encoding=”UTF-8”?>


Archetype Created Web Application

<!--三个servlet配置   /login跳转到登陆页面  /home跳转到主页,即登陆成功页面 /admin用来测试角色和权限-->
<servlet>
    <servlet-name>loginServlet</servlet-name>
    <servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>loginServlet</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
    <servlet-name>homeServlet</servlet-name>
    <servlet-class>com.servlet.HomeServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>homeServlet</servlet-name>
    <url-pattern>/home</url-pattern>
</servlet-mapping>
<servlet>
    <servlet-name>adminServlet</servlet-name>
    <servlet-class>com.servlet.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>adminServlet</servlet-name>
    <url-pattern>/admin</url-pattern>
</servlet-mapping>

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

<!--shiro过滤器 这里过滤所有的地址 并且指定权限配置文件(一般项目中权限的配置存放在数据库中)-->
<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    <init-param>
        <param-name>config</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>


pom.xml :所需依赖



org.apache.shiro
shiro-web
1.3.2


org.apache.shiro
shiro-core
1.3.2


org.apache.shiro
shiro-spring
1.3.2


org.apache.tomcat
tomcat-servlet-api
8.5.4
provided


org.slf4j
slf4j-log4j12
1.7.7
compile


commons-logging
commons-logging
1.2


commons-beanutils
commons-beanutils
1.9.3


commons-collections
commons-collections
3.2.1


jstl
jstl
1.2


shiro.ini: 权限配置文件,配置什么用户有什么角色,什么角色有什么权限

[main]
authc.loginUrl=/login
perms.unauthorizedUrl=/noAuth.jsp
roles.unauthorizedUrl=/noAuth.jsp
[users]
zj2626=123456,admin
ay2626=456789,student
[roles]
admin=user:,student:select
student:student:

[urls]
/login=anon
/home=authc
/admin=roles[admin]
1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址…(loginUrl是authc的一个属性)

2.roles.unauthorizeUrl配置角色认证不通过跳转的地址…(noAuth.jsp页面目前只有一行字)

3.perms.unauthorizeUrl配置权限认证不通过跳转的地址

4.[users]下配置用户身份信息以及用户角色

5.[roles]下配置角色以及角色的控制权限

6.[urls]下配置访问地址所需的权限, 其中值为”anon过滤器”表示地址不需要登录即可访问; “authc过滤器”表示地址登录才能访问

7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围

8.值为 perms[“student:create”] 表示 必须有权限为”student:create”的用户才能范围

9.多个过滤器用”,”隔开 而且相互为”且”的关系(必须同时满足才能访问)

10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef…..)

11.地址可以使用表示匹配任意个任意字符(eg: /home=authc 表示可过滤 /home123; /homeef…..)

12.地址可以使用表示匹配多路径(eg: /home/=authc 表示可过滤 /home/abc; /home/aaa/bbb…..)

2.编写sevlet代码

LoginServlet.java :身份验证地址

package com.servlet;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**

  • Created by zj on 2017/4/10.
    */
    public class LoginServlet extends HttpServlet {
    /**

    • 跳转登录界面
      *
    • @param req
    • @param resp
    • @throws ServletException
    • @throws IOException
      */
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      System.out.println(“user no login”);
      resp.sendRedirect(“login.jsp”);
      }

      /**

    • 进行登录
      *
    • @param req
    • @param resp
    • @throws ServletException
    • @throws IOException
      */
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      System.out.println(“登录”);
      String userName = req.getParameter(“userName”);
      String password = req.getParameter(“password”);

      Subject subject = SecurityUtils.getSubject();
      //创建token令牌, 用户/密码
      UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
      try {

       //身份认证 登录
       subject.login(token);
       System.out.println("登录成功");
       resp.sendRedirect("success.jsp");
      

      } catch (Exception e) {

       System.out.println("账号密码不对");
       e.printStackTrace();
       resp.sendRedirect("login.jsp");
      

      }
      }
      }

//**
login.jsp

<%@ page language=”java” pageEncoding=”UTF-8” %>
<%@ taglib uri=”http://java.sun.com/jsp/jstl/core" prefix=”c” %>
<!DOCTYPE HTML>

Hello World


登录:








HomeServlet.java :登录成功以及退出登录地址

package com.servlet;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**

  • Created by zj on 2017/4/10.
    */
    public class HomeServlet extends HttpServlet {
    /**

    • 进入主页(登陆成功界面)
      *
    • @param req
    • @param resp
    • @throws ServletException
    • @throws IOException
      */
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      System.out.println(“主页 get”);

      req.getRequestDispatcher(“success.jsp”).forward(req, resp);

      }

      /**

    • 用来退出登陆
      *
    • @param req
    • @param resp
    • @throws ServletException
    • @throws IOException
      */
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      System.out.println(“主页 post”);

      System.out.println(“login out”);

      //退出登录
      Subject subject = SecurityUtils.getSubject();
      subject.logout();

      resp.sendRedirect(“login.jsp”);
      }
      }

//**
success.jsp

<%@ page contentType=”text/html;charset=UTF-8” language=”java” %>

Hello World!

成功!!!






AdminServlet.java

package com.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**

  • Created by zj on 2017/4/10.
    */
    public class AdminServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

     System.out.println("ADMIN GET");
    

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

     System.out.println("ADMIN POST");
    

    }
    }
    3.启动项目

测试身份认证

不登录情况下输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面

输入正确的用户名(ay2626)密码 点击登录 成功; 再次输入地址 http://localhost:8080/home 跳转到成功的jsp

点击退出登录 成功; 再次输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面

测试角色认证

登录 :进行其他认证前先进行身份认证 使用ay2626用户登录(其角色只有student) 登录成功 跳转到success.jsp

输入地址http://localhost:8080/admin 跳转到 http://localhost:8080/noAuth.jsp 表示没有权限访问此地址

退出登录 用zj2626再次登录测试 登录成功 再次输入http://localhost:8080/admin 控制台打印”ADMIN POST” 表示访问成功

测试权限认证

可把配置文件中 /admin的过滤器改为 /admin=perms[“student:create”] 进行测试

测试发现 有权限的ay2626用户可以访问而没有权限的zj2626不能访问


自定义Realm

实际开发中用户权限的配置要存放在数据库,so需要使用自定义realm来读取数据库中权限配置然后为用户赋予权限

测试开发步骤

1.添加数据库依赖


mysql
mysql-connector-java
5.1.39
compile

2.设计构建测试的数据库表:有三个表:分别存储用户,角色,权限 并其他两个都与角色关联, 然后存入部分数据 ,再根据三个表建立对应实体(简单实体)

3.添加数据库操作类(写的并不完善 仅测试使用)

package com.servlet;

import java.sql.*;

/**

  • Created by zj on 2017/4/11.
    */
    public class DBUtil {

    //获取数据库连接
    private static Connection getConnection() {

     try {
         Class.forName("com.mysql.jdbc.Driver");
    
         Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "fangshuoit");
    
         return connection;
     } catch (ClassNotFoundException | SQLException e) {
         e.printStackTrace();
     }
     return null;
    

    }

    /**

    • 通过用户名获取用户信息
      *
    • @param name
    • @return
    • @throws SQLException
      /
      public static User getByUserName(String name) throws SQLException {
      String sql = “select
      from ay_user where loginName = ?”;

      PreparedStatement preparedStatement = getConnection().prepareStatement(sql);
      preparedStatement.setString(1, name);

      ResultSet resultSet = preparedStatement.executeQuery();
      if (resultSet.next()) {

       return new User(resultSet.getInt("id"), resultSet.getString("loginName"), resultSet.getString("password"));
      

      }

      return null;
      }

      /**

    • 通过用户名获取用户角色
      *
    • @param name
    • @return
    • @throws SQLException
      */
      public static Role getRolesByUserName(String name) throws SQLException {
      String sql = “select roleId from ay_user where loginName = ?”;

      PreparedStatement preparedStatement = getConnection().prepareStatement(sql);
      preparedStatement.setString(1, name);

      ResultSet resultSet = preparedStatement.executeQuery();
      if (resultSet.next()) {

       Integer id = resultSet.getInt("roleId");
      
       sql = "select * from ay_role where id = ?";
       preparedStatement = getConnection().prepareStatement(sql);
       preparedStatement.setInt(1, id);
       resultSet = preparedStatement.executeQuery();
       if (resultSet.next()) {
           return new Role(resultSet.getInt("id"), resultSet.getString("name"));
       }
      

      }

      return null;
      }

      /**

    • 通过角色id获取角色权限
      *
    • @param roleId
    • @return
    • @throws SQLException
      /
      public static Perms getPermsByRole(Integer roleId) throws SQLException {
      String sql = “select
      from ay_perms where roleId = ?”;

      PreparedStatement preparedStatement = getConnection().prepareStatement(sql);
      preparedStatement.setInt(1, roleId);

      ResultSet resultSet = preparedStatement.executeQuery();
      if (resultSet.next()) {

       return new Perms(resultSet.getInt("id"), resultSet.getString("name"), resultSet.getInt("roleId"));
      

      }

      return null;
      }
      }
      3.编写自定义的Realm类 需要AuthorizingRealm类并实现两个方法; 第一个是用来身份验证,第二是用来角色权限验证

public class MyRealm extends AuthorizingRealm {

/**
 * 验证当前登录的用户(身份认证), 不再需要在配置文件中配置用户的信息和其角色信息
 * 
 * @param token 封装有用户的信息
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String userName = (String) token.getPrincipal();

    System.out.println("要登录的用户 : " + userName);

    try {
        User user = DBUtil.getByUserName(userName);//这里只是通过用户名验证并获取用户信息,实际开发中需要用户名以及加密的密码
        if (user != null) {
            return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), "XX");//返回登录信息
        } else
            return null;
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return null;
}

/**( 身份验证(登录)以后 )
 * 为当前用户授予角色和权限(根据登录的用户名,读取数据库中其角色和权限)
 *
 * @param principals 封装了身份信息
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String userName = (String) principals.getPrimaryPrincipal();
    System.out.println("要权限的用户 : " + userName);

    SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();

    try {
        Role role = DBUtil.getRolesByUserName(userName);
        if (null != role) {
            Set<String> set = new HashSet<>();
            System.out.println("获得的角色: "+ role.getName());
            set.add(role.getName());
            authenticationInfo.setRoles(set);//赋予角色

            Perms perms = DBUtil.getPermsByRole(role.getId());
            Set<String> set2 = new HashSet<>();
            set2.add(perms.getName());
            System.out.println("获得的权限: "+ perms.getName());
            authenticationInfo.setStringPermissions(set2);//赋予权限

            return authenticationInfo;
        }
    } catch (SQLException e) {

    }

    return null;
}

}
4.修改配置文件shiro.ini ,引入自定义Realms,并去掉原来的 [users]和[roles]下的配置

[main]
authc.loginUrl=/login
perms.unauthorizedUrl=/noAuth.jsp
roles.unauthorizedUrl=/noAuth.jsp

myRealm=com.servlet.MyRealm
securityManager.realms=$myRealm
[urls]
/login=anon
/home=authc
/admin=roles[admin]
1.myRealm指向自定义Realm的位置

2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用”,”隔开

5.启动项目测试 发现:使用zj2626登录可以访问 /admin地址;而ay2626登录不能访问(没有user角色);而且每次请求都会进行认证(控制台打印信息)

Shiro


Reprint please specify: Blog4Jun Shiro的学习Helloworld

Previous
Spring+quartz实现任务调度的小例子 Spring+quartz实现任务调度的小例子
实现任务调度分为三大模块:1.任务调度器Scheduler; 2.触发器cronTrigger;
2018-12-04 Pursue
Next
Servlet的生命周期 Servlet的生命周期
12.Servlet的生命周期1)加载:在下列时刻加载 Servlet:(1)如果已配置自动加载选项,则在启动服务器时自动加载 (web.xml中 设置);(2)在服务器启动后,客户机首次向 Servlet 发出请求时;(3)
2018-12-04 Pursue