Apache Shiro 是一個功能強大且靈活的開放源代碼安全框架,能夠細粒度地處理認證 (Authentication),受權 (Authorization),會話 (Session) 管理和加密 (cryptography) 等企業級應用中常見的安全控制流程。 Apache Shiro 的首要目標是易於使用和理解。 有時候安全性的流程控制會很是複雜,對開發人員來講是件很頭疼的事情,但並不必定如此。 框架就應該儘量地掩蓋複雜性,並公開一個簡潔而直觀的 API,從而簡化開發人員的工做,確保其應用程序安全性。此次咱們聊一聊如何在 Spring Web 應用中使用 Shiro 實現權限控制。java
Apache Shiro 是一個具備許多功能的綜合型應用程序安全框架。 下圖爲 Shiro 中的最主要的幾個功能: web
從總體概念上理解,Shiro 的體系架構有三個主要的概念:Subject (主體,也就是用戶),Security Manager (安全管理器)和 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";
}
}
複製代碼