1. 程式人生 > 實用技巧 >shiro安全框架-使用者認證授權以及許可權控制

shiro安全框架-使用者認證授權以及許可權控制

使用shiro之前肯定要匯入相關的依賴

 <!-- shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--thymeleaf-shiro整合-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

說明:最後一個是thymeleaf的整合包,前端如果需要使用標籤就需要這個包。

使用shiro驗證使用者登入

資料庫表

說明:密碼是使用鹽值加密進行處理後存放在資料庫中的,salt是鹽值。

spring-shiro.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
      http://www.springframework.org/schema/util
      http://www.springframework.org/schema/util/spring-util.xsd">
    <!--url過濾器-->
    <bean id="urlPathMatchingFilter" class="www.han.filter.UrlPathMatchingFilter"/>

    <!-- 登出出過濾器 -->
    <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="/"/>
    </bean>

    <!--配置shiro的過濾器工廠類,id- shiroFilter要和我們在web.xml中配置的過濾器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 呼叫我們配置的許可權管理器 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 配置我們的登入請求地址 -->
        <property name="loginUrl" value="/router/toLogin"/>
        <!-- 如果您請求的資源不再您的許可權範圍,則跳轉到/403請求地址 -->
        <property name="unauthorizedUrl" value="/router/nopower"/>
        <!-- 過濾器 -->
        <property name="filters">
            <util:map>
                <entry key="logout" value-ref="logoutFilter"/>
                <entry key="url" value-ref="urlPathMatchingFilter"/>
            </util:map>
        </property>
        <!-- 許可權配置 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- anon表示此地址不需要任何許可權即可訪問 -->
                /=anon
                /static/**=anon
                /router/*=anon
                /*.html=anon
                /login.do=anon
                /register.do=anon
                /logout=logout

                <!--/staff/**=authc-->
                <!-- 所有的請求(除去配置的靜態資源請求或請求地址為anon的請求)都要通過登入驗證,如果未登入則跳到/login -->
                /**=url
            </value>
        </property>
    </bean>

    <!-- 會話ID生成器 -->
    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

    <!-- 會話Cookie模板 關閉瀏覽器立即失效 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="-1"/>
    </bean>

    <!-- 會話DAO -->
    <bean id="sessionDAO"
          class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    </bean>

    <!-- 會話驗證排程器,每30分鐘執行一次驗證 ,設定會話超時及儲存 -->
    <bean name="sessionValidationScheduler"
          class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
        <property name="interval" value="1800000"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

    <!-- 會話管理器 -->
    <bean id="sessionManager"
          class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 全域性會話超時時間(單位毫秒),預設30分鐘 -->
        <property name="globalSessionTimeout" value="1800000"/>
        <property name="deleteInvalidSessions" value="true"/>
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <property name="sessionDAO" ref="sessionDAO"/>
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="databaseRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

    <!-- 相當於呼叫SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod"
                  value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>

    <!-- 密碼匹配器,自動校驗密碼 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="1"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>
    <!--注入DatabaseRealm類-->
    <bean id="databaseRealm" class="www.han.config.shiro.UserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>

    <!-- 保證實現了Shiro內部lifecycle函式的bean執行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

注意/**=url要放在最後面,如果放在前面就先執行的url過濾,靜態資源和開放許可權的跳轉會失效。

shiroUtil(對密碼進行加密)

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

public class ShiroUtil {
    public static String encryptPassword(String password, String salt) {
        // 在這裡更改密碼規則後還要在applicationContext-shiro.xml中改密碼匹配器
        return new SimpleHash("md5", password, salt, 1).toString();
    }

    public static String generateSalt() {
        return new SecureRandomNumberGenerator().nextBytes().toString();
    }
}

寫核心的授權驗證realm

package www.han.config.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import www.han.pojo.User;
import www.han.service.PermService;
import www.han.service.RoleService;
import www.han.service.UserService;

import java.util.Set;

//自定義Realm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    @Autowired
    private PermService permService;
    @Autowired
    private RoleService roleService;
    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //賬號已經通過驗證了
        String userName =(String) principalCollection.getPrimaryPrincipal();
        System.out.println("授權中得到的userName:"+userName);
        //通過service獲取角色和許可權
        Set<String> permissions = permService.getPerms(userName);
        Set<String> roles = roleService.getRoles(userName);
        //授權物件
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        //把通過service獲取到的角色和許可權放進去
        s.setStringPermissions(permissions);
        s.setRoles(roles);
        return s;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("執行了認證+===》doGetAuthenticationInfo");
        // 獲取名字
        String name = token.getPrincipal().toString();
        System.out.println(name);

        // 獲取user
        User admin = userService.loginByName(name);
        if (admin == null){
           return null;
        }
        String passwordInDB = admin.getUser_password();
        String salt = admin.getSalt();
        return new SimpleAuthenticationInfo(name, passwordInDB, ByteSource.Util.bytes(salt), getName());
        // getName()是realm的繼承方法,返回當前類名DatabaseRealm
        // 通過applicationContext-shiro.xml中的HashedCredentialsMatcher,進行密碼的自動校驗
    }
}

登入以及註冊控制

   @RequestMapping(value="/login.do",method = RequestMethod.POST)
    public String loginControl(String username, String password,  Model model){
        //獲取當前使用者,shiro包下的
        Subject subject = SecurityUtils.getSubject();
        //封裝使用者的登入資料
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //執行登入方法
        try {
            subject.login(token);//執行登入,在使用者認證的裡面去認證使用者名稱和密碼
            Session session=subject.getSession();
            session.setAttribute("subject", subject);
            return "redirect:/router/toIndex";//登入通過轉到首頁
        }catch (UnknownAccountException e){//使用者名稱不存在
            model.addAttribute("msg","使用者名稱不存在");
            return "login";
        }catch (IncorrectCredentialsException e){//密碼不存在
            model.addAttribute("msg","密碼錯誤啊");
            return "login";
        }
    }

 @RequestMapping(value = "/register.do",method = RequestMethod.POST)
    public String register(String user_id,String user_name,String user_password,String user_phone,Model model){
        User user = new User();
        user.setUser_id(Integer.parseInt(user_id));
        user.setUser_name(user_name);
        user.setUser_password(user_password);
        user.setUser_phone(user_phone);

        String salt = new Random().nextInt(1000000)+"";//通過隨機數來設定鹽值
        user.setSalt(salt);
        String encryptPassword = ShiroUtil.encryptPassword(user.getUser_password(), user.getSalt());//將密碼和鹽值混合加密
        user.setUser_password(encryptPassword);
        int register = userService.register(user);
        if (register > 0){
            //進行角色分配,註冊的角色都設定為普通使用者
            int i = roleService.addRole(user.getUser_id());
            if (i <= 0){
                model.addAttribute("msg","賬號註冊成功!角色分配失敗,請聯絡管理要換進行許可權分配!");
                System.out.println("角色分配失敗!請手動進行分配!");
            }
            model.addAttribute("msg","註冊成功!");
            return "login";
        }else{
            return "login";
        }
    }

說明:資料庫中對應的有一個許可權表,使用者表,角色表,使用者對應的角色表,角色對應的許可權表,通過這幾個表就能輕鬆實現不同使用者的角色分配以及許可權分配。

url過濾器(因為採用的是許可權控制用url)

package www.han.filter;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import www.han.service.PermService;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;

public class UrlPathMatchingFilter extends PathMatchingFilter {
  @Autowired
   PermService permService;

  @Override
  protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
      System.out.println("進來了PathMatchingFilter");
      String requestUrl = getPathWithinApplication(request);
      System.out.println("PathMatchingFilter---"+requestUrl);
      Subject subject = SecurityUtils.getSubject();
      if(!subject.isAuthenticated()){
          // 未登入
          WebUtils.issueRedirect(request,response,"/router/toLogin");
          return false;
      }
      System.out.println("permService-----"+permService);
      boolean needInterceptor = permService.needInterceptor(requestUrl);
     // System.out.println("是否需要許可權驗證:"+needInterceptor);

      if(!needInterceptor){//如果資料庫中沒有該許可權的資訊,就直接放行
          return true;
      }else{
          boolean hasPermission = false;
          String userName = subject.getPrincipal().toString();
          Set<String> permissionUrls = permService.getUrl(userName);
          for(String url:permissionUrls){
              if(requestUrl.equals(url)){
                  hasPermission = true;
              }
          }
          if(hasPermission){
              return true;
          }else{
              UnauthorizedException e =  new UnauthorizedException("當前使用者沒有訪問"+requestUrl+"的許可權");
              subject.getSession().setAttribute("e",e)
              WebUtils.issueRedirect(request,response,"/router/nopower");
              return false;
          }
      }
  }
}

許可權控制

  • 在資料庫中存放需要有許可權操作的請求路徑,然後前端可以用標籤來驗證使用者是否擁有某個許可權從而達到不同的資料顯示給不同級別的使用者。

完整專案地址