Spring MVC 整合 Shiro 權限控制

Apache Shiro 是一個功能強大且靈活的開放源代碼安全框架,能夠細粒度地處理認證 (Authentication),受權 (Authorization),會話 (Session) 管理和加密 (cryptography) 等企業級應用中常見的安全控制流程。 Apache Shiro 的首要目標是易於使用和理解。 有時候安全性的流程控制會很是複雜,對開發人員來講是件很頭疼的事情,但並不必定如此。 框架就應該儘量地掩蓋複雜性,並公開一個簡潔而直觀的 API,從而簡化開發人員的工做,確保其應用程序安全性。此次咱們聊一聊如何在 Spring Web 應用中使用 Shiro 實現權限控制。java

功能

Apache Shiro 是一個具備許多功能的綜合型應用程序安全框架。 下圖爲 Shiro 中的最主要的幾個功能: web

Shiro 的主要目標是「應用安全的四大基石」 - 認證,受權,會話管理和加密:

  • 身份驗證:也就是一般所說的 「登陸」,爲了證實用戶的行爲全部者。
  • 受權:訪問控制的過程,即肯定什麼用戶能夠訪問哪些內容。
  • 會話管理:即便在非 Web 應用程序中,也能夠管理用戶特定的會話,這也是 Shiro 的一大亮點。
  • 加密技術:使用加密算法保證數據的安全,很是易於使用。

架構

從總體概念上理解,Shiro 的體系架構有三個主要的概念:Subject (主體,也就是用戶),Security Manager (安全管理器)和 Realms (領域)。 下圖描述了這些組件之間的關係: 算法

這幾大組件能夠這樣理解:

  • Subject (主體):主體是當前正在操做的用戶的特定數據集合。主體能夠是一我的,也能夠表明第三方服務,守護進程,定時任務或相似的東西,也就是幾乎全部與該應用進行交互的事物。
  • Security Manager (安全管理器):它是 Shiro 的體系結構的核心,扮演了相似於一把 「傘」 的角色,它主要負責協調內部的各個組件,造成一張安全網。
  • Realms (領域):Shiro 與應用程序安全數據之間的 「橋樑」。當須要實際與用戶賬戶等安全相關數據進行交互以執行認證和受權時,Shiro 將從 Realms 中獲取這些數據。

數據準備

在 Web 應用中,對安全的控制主要有角色、資源、權限(什麼角色能訪問什麼資源)幾個概念,一個用戶能夠有多個角色,一個角色也能夠訪問多個資源,也就是角色能夠對應多個權限。落實到數據庫設計上,咱們至少須要建 5 張表:用戶表、角色表、資源表、角色-資源表、用戶-角色表,這 5 張表的結構以下: 用戶表:spring

id username password
1 張三 123456
2 李四 666666
3 王五 000000

角色表:數據庫

id rolename
1 管理員
2 經理
3 員工

資源表:apache

id resname
1 /user/add
2 /user/delete
3 /compony/info

角色-資源表:緩存

id roleid resid
1 1 1
2 1 2
3 2 3

用戶-角色表:安全

id userid roleid
1 1 1
2 1 2
3 1 3

對應的 POJO 類以下:session

/** * 用戶 */
public class User {
 private Integer id;
 private String username;
 private String password;
    //getter & setter...
}
複製代碼
/** * 角色 */
public class Role {
    private String id;
    private String rolename;
}
複製代碼
/** * 資源 */
public class Resource {
    private String id;
    private String resname;
}
複製代碼
/** * 角色-資源 */
public class RoleRes {
    private String id;
    private String roleid;
    private String resid;
}
複製代碼
/** * 用戶-角色 */
public class UserRole {
    private String id;
    private String userid;
    private String roleid;
}
複製代碼

Spring 與 Shiro 整合的詳細步驟,請參閱個人博客 《 Spring 應用中整合 Apache Shiro 》。 這裏補充一下:須要提早引入 Shiro 的依賴,打開 http://www.javashuo.com/tag/mvnrepository.com,搜索 Shiro,咱們須要前三個依賴,也就是 Shiro-Core、Shiro-Web 以及 Shiro-Spring,以 Maven 項目爲例,在 pom.xml 中的 <dependencies> 節點下添加以下依賴:架構

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
複製代碼

application-context.xml 中須要這樣配置 shiroFilter 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="/login"/>
    <!-- 登陸成功後的頁面 -->
    <property name="successUrl" value="/index"/>
    <!-- 非法訪問跳轉的頁面 -->
    <property name="unauthorizedUrl" value="/403"/>
    <!-- 權限配置 -->
    <property name="filterChainDefinitions">
        <value>
            <!-- 無需認證便可訪問的靜態資源,還能夠添加其餘 url -->
            /static/** = anon
            <!-- 除了上述忽略的資源,其餘全部資源都須要認證後才能訪問 -->
            /** = authc
        </value>
    </property>
</bean>
複製代碼

接下來就須要定義 Realm 了,自定義的 Realm 集成自 AuthorizingRealm 類:

public class MyRealm extends AuthorizingRealm {
 @Autowired
 private UserService userService;
 /** * 驗證權限 */
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  String loginName = SecurityUtils.getSubject().getPrincipal().toString();
  if (loginName != null) {
   String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString();
   // 權限信息對象,用來存放查出的用戶的全部的角色及權限
   SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
   // 用戶的角色集合
   ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
         info.setRoles(shiroUser.getRoles());
         info.addStringPermissions(shiroUser.getUrlSet());
   return info;
  }
  return null;
 }
 /** * 認證回調函數,登陸時調用 */
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
  String username = (String) token.getPrincipal();
  User user = new User();
        sysuser.setUsername(username);
  try {
   List<SysUser> users = userService.findByNames(user);
            List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId());
   if (users.size() != 0) {
    String pwd = users.get(0).getPassword();
    // 當驗證都經過後,把用戶信息放在 session 裏
    Session session = SecurityUtils.getSubject().getSession();
    session.setAttribute("userSession", users.get(0));
    session.setAttribute("userSessionId", users.get(0).getId());
    session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,","));
                return new SimpleAuthenticationInfo(username,users.get(0).getPassword());
   } else {
                // 沒找到該用戶
    throw new UnknownAccountException();
   }
  } catch (Exception e) {
   System.out.println(e.getMessage());
  }
  return null;
 }
 /** * 更新用戶受權信息緩存. */
 public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
  super.clearCachedAuthorizationInfo(principals);
 }
 /** * 更新用戶信息緩存. */
 public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
  super.clearCachedAuthenticationInfo(principals);
 }
 /** * 清除用戶受權信息緩存. */
 public void clearAllCachedAuthorizationInfo() {
  getAuthorizationCache().clear();
 }
 /** * 清除用戶信息緩存. */
 public void clearAllCachedAuthenticationInfo() {
  getAuthenticationCache().clear();
 }
 /** * 清空全部緩存 */
 public void clearCache(PrincipalCollection principals) {
  super.clearCache(principals);
 }
 /** * 清空全部認證緩存 */
 public void clearAllCache() {
  clearAllCachedAuthenticationInfo();
  clearAllCachedAuthorizationInfo();
 }
}
複製代碼

最後定義一個用戶登陸的控制器,接受用戶的登陸請求:

@Controller
public class UserController {
    /** * 用戶登陸 */
    @PostMapping("/login")
    public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){
        try {
            if(bindingResult.hasErrors()){
                return "login";
            }
            //使用權限工具進行認證,登陸成功後跳到 shiroFilter bean 中定義的 successUrl
            SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
            return "redirect:index";
        } catch (AuthenticationException e) {
            redirectAttributes.addFlashAttribute("message","用戶名或密碼錯誤");
            return "redirect:login";
        }
    }
    /** * 註銷登陸 */
    @GetMapping"/logout")
    public String logout(RedirectAttributes redirectAttributes ){
        SecurityUtils.getSubject().logout();
        return "redirect:login";
    }
}
複製代碼
相關文章
相關標籤/搜索