今天研究一下 經常使用的安全框架shirocss
先好好研究一下shiro 的實現原理,方便後面的學習html
shiro (java安全框架)java
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、受權、密碼和會話管理。使用Shiro的易於理解的API,您能夠
快速、輕鬆地得到任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
軟件名稱 Apache Shiro 開發商 Apache 性質 Java安全框架
複製代碼
主要功能 三個核心組件:Subject, SecurityManager 和 Realms.git
Subject:即「當前操做用戶」。可是,在Shiro中,Subject這一律念並不只僅指人,也能夠是第三方進程、後臺賬戶
(Daemon Account)或其餘相似事物。它僅僅意味着「當前跟軟件交互的東西」。但考慮到大多數目的和用途,你能夠把它認爲
是Shiro的「用戶」概念。Subject表明了當前用戶的安全操做,SecurityManager則管理全部用戶的安全操做。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro經過SecurityManager來管理內部組件實例,並經過它來
提供安全管理的各類服務。
Realm: Realm充當了Shiro與應用安全數據間的「橋樑」或者「鏈接器」。也就是說,當對用戶執行認證(登陸)和受權(訪問控
制)驗證時,Shiro會從應用配置的Realm中查找用戶及其權限信息。從這個意義上講,Realm實質上是一個安全相關的DAO:它
封裝了數據源的鏈接細節,並在須要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(
或)受權。配置多個Realm是能夠的,可是至少須要一個。 Shiro內置了能夠鏈接大量安全數據源(又名目錄)的Realm,
如LDAP、關係數據庫(JDBC)、相似INI的文本配置資源以及屬性文件等。若是缺省的Realm不能知足需求,你還能夠插入表明
自定義數據源的本身的Realm實現。
複製代碼
shiro 原理剖析: shiro的核心是java servlet規範中的filter,經過配置攔截器,使用攔截器鏈來攔截請求,若是容許訪問,則經過。一般狀況下,系統的登陸、退出會配置攔截器。登陸的時候,調用subject.login(token),token是用戶驗證信息,這個時候會在Realm中doGetAuthenticationInfo方法中進行認證。這個時候會把用戶提交的驗證信息與數據庫中存儲的認證信息進行比較,一致則容許訪問,並在瀏覽器種下這次回話的cookie,在服務器端存儲session信息。退出的時候,調用subject.logout(),會清除回話信息。github
shiro中核心概念介紹: Filter:redis
1.AnonymousFilter:經過此filter修飾的url,任何人均可以進行訪問,即便沒有進行權限認證spring
2.FormAuthenticationFilter:經過此filter修飾的url,會對請求的url進行驗證,若是沒有經過,則會重定向返回到loginurlsql
3.BasicHttpAuthenticationFilter:經過此filter修飾的url,要求用戶已經經過認證,若是沒有經過,則會要求經過Authorization 信息進行認證數據庫
4.LogoutFilter:經過此filter修飾的url,一旦收到url請求,則會當即調用subject進行退出,並重定向到redirectUrlapache
5.NoSessionCreationFilter:經過此filter修飾的url,不會建立任何會話
6.PermissionAuthorizationFilter:權限攔截器,驗證用戶是否具備相關權限
7.PortFilter:端口攔截器,不是經過制定端口訪問url,將自動將端口重定向到指定端口
8.HttpMethodPermissionFilter:rest風格攔截器,配置rest的訪問方式
9.RolesAuthorizationFilter:角色攔截器,未登錄,將跳轉到loginurl,未受權,將跳轉到unauthorizedUrl
10.SslFilter:HTTPS攔截器,須要以HTTPS的方式進行訪問
11.UserFilter:用戶攔截器,須要用戶已經認證,或已經remember me
攔截配置說明:
anon:例子/admins/**=anon 沒有參數,表示能夠匿名使用。
authc:例如/admins/user/**=authc表示須要認證(登陸)才能使用,沒有參數
roles:例子/admins/user/**=roles[admin],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每一個參數經過纔算經過,至關於hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:*],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每一個參數都經過才經過,想當於isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根據請求的方法,至關於/admins/user/**=perms[user:method] ,其中method爲post,get,delete等。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString是你訪問的url裏的?後面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操做時不作檢查
注:anon,authcBasic,auchc,user是認證過濾器,
perms,roles,ssl,rest,port是受權過濾器
複製代碼
shiro 的權限控制只是作到資源的權限控制,要想實現業務數據的權限控制,確定是須要耦合到咱們具體的業務代碼裏面的,後面有時間在分享一下如今本身公司的一種解決思路。
上面簡單介紹了一下shiro,接下來就進入正題。
加入須要的依賴
<!-- spring-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--模板引擎,訪問靜態資源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro 相關包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro+redis緩存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
<!--shiro與thymelef的集成插件-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
複製代碼
yml文件在加入一點配置,這裏要注意的是jpa 和以前的datasource 處於同一級
#經過jpa 生成數據庫表
spring:
jpa:
hibernate:
ddl-auto: update
show-sql: true
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
複製代碼
數據庫設計 通常的權限管理都會設計到這五張表(用戶表、角色表、用戶角色中間表、權限表、角色權限中間表)
一、用戶表:
@Entity //實體類的註解
@Table(name="sys_user")
public class SysUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String passWord;
private int userEnable;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public int getUserEnable() {
return userEnable;
}
public void setUserEnable(int userEnable) {
this.userEnable = userEnable;
}
}
複製代碼
二、角色表
@Entity //實體類的註解
@Table(name="sys_role")
public class SysRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String roleName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
複製代碼
三、用戶角色中間表
@Entity //實體類的註解
@Table(name="sys_user_role")
public class SysUserRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int userId;
private int roleId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
}
複製代碼
四、權限表
@Entity //實體類的註解
@Table(name="sys_permission")
public class SysPermission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String resUrl;
private String userType;
private String parentId;
private String userSort;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getResUrl() {
return resUrl;
}
public void setResUrl(String resUrl) {
this.resUrl = resUrl;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getUserSort() {
return userSort;
}
public void setUserSort(String userSort) {
this.userSort = userSort;
}
}
複製代碼
五、角色權限中間表
@Entity //實體類的註解
@Table(name="sys_role_permission")
public class SysRolePermission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int roleId;
private int permissionId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
public int getPermissionId() {
return permissionId;
}
public void setPermissionId(int permissionId) {
this.permissionId = permissionId;
}
}
複製代碼
基本上五個表的結構就是這樣,有問題到時候再改,實體類建好了,先建數據庫也行都同樣,如今先建了實體類就經過spring-data-jpa 生成一下數據庫的表結構 這時候在重啓項目就能夠幫咱們在數據庫建好表了。
shiro 配置類
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 沒有登錄的用戶只能訪問登錄頁面
shiroFilterFactoryBean.setLoginUrl("/auth/login");
// 登陸成功後要跳轉的連接
shiroFilterFactoryBean.setSuccessUrl("/auth/index");
// 未受權界面; ----這個配置了沒卵用,具體緣由想深刻了解的能夠自行百度
shiroFilterFactoryBean.setUnauthorizedUrl("/auth/err");
//自定義攔截器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
shiroFilterFactoryBean.setFilters(filtersMap);
// 權限控制map.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/auth/logout", "logout");
filterChainDefinitionMap.put("/auth/kickout", "anon");
//filterChainDefinitionMap.put("/book/**", "authc,perms[book:list],roles[admin]");
//filterChainDefinitionMap.put("/**", "authc,kickout");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設置realm.
securityManager.setRealm(myShiroRealm());
// 自定義緩存實現 使用redis
securityManager.setCacheManager(cacheManager());
// 自定義session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 身份認證realm; (這個須要本身寫,帳號密碼校驗;權限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
/**
* cacheManager 緩存 redis實現
* 使用的是shiro-redis開源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis開源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
redisManager.setExpire(1800);// 配置緩存過時時間
redisManager.setTimeout(0);
// redisManager.setPassword(password);
return redisManager;
}
/**
* Session Manager
* 使用的是shiro-redis開源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao層的實現 經過redis
* 使用的是shiro-redis開源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/***
* 受權所用配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/***
* 使受權註解起做用不如不想配置能夠在pom文件中加入
* <dependency>
*<groupId>org.springframework.boot</groupId>
*<artifactId>spring-boot-starter-aop</artifactId>
*</dependency>
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命週期處理器
*
*/
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
複製代碼
自定義Realm:
public class MyShiroRealm extends AuthorizingRealm {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
//若是項目中用到了事物,@Autowired註解會使事物失效,能夠本身用get方法獲取值
@Autowired
private SysRoleService roleService;
@Autowired
private UserService userService;
/**
* 認證信息.(身份驗證) : Authentication 是用來驗證用戶身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
logger.info("---------------- 執行 Shiro 憑證認證 ----------------------");
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
SysUser user = new SysUser();
user.setUserName(name);
user.setPassWord(password);
// 從數據庫獲取對應用戶名密碼的用戶
SysUser userList = userService.getUser(user);
if (userList != null) {
// 用戶爲禁用狀態
if (userList.getUserEnable() != 1) {
throw new DisabledAccountException();
}
logger.info("---------------- Shiro 憑證認證成功 ----------------------");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userList, //用戶
userList.getPassWord(), //密碼
getName() //realm name
);
return authenticationInfo;
}
throw new UnknownAccountException();
}
/**
* 受權
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("---------------- 執行 Shiro 權限獲取 ---------------------");
Object principal = principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if (principal instanceof SysUser) {
SysUser userLogin = (SysUser) principal;
Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId());
authorizationInfo.addRoles(roles);
Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId());
authorizationInfo.addStringPermissions(permissions);
}
logger.info("---- 獲取到如下權限 ----");
logger.info(authorizationInfo.getStringPermissions().toString());
logger.info("---------------- Shiro 權限獲取成功 ----------------------");
return authorizationInfo;
}
/**
* 清除全部用戶受權信息緩存.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除全部用戶受權信息緩存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
/**
* @Description: TODO 清楚緩存的受權信息
* @return void 返回類型
*/
public void clearAuthz(){
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
複製代碼
在數據庫裏面插入幾條數據開始測試。
就拿以前的代碼作測試了,先在配置裏配置好須要作權限過濾的路徑,和權限規則
這樣設置應該是必須登陸才能訪問,瀏覽器直接訪問一下
結果發現跳轉到以前配置的登陸頁了,就是說這個權限起做用了,把它改爲anon,重啓試一下 ,其實在啓動的控制檯咱們能看到shiroFilter這個過濾器的信息,過濾的是 /*在訪問一下以前的連接,發現能夠正常訪問到,查到了以前的測試數據
改個角色的權限試試在,以前咱們已經給用戶設置了 ‘admin’ ‘test’兩個角色 , 沒有設置‘demo’這個角色,請求應該也會被攔截
果真又跳到了登陸頁面,把‘demo’ 去掉,發現能夠正常請求查到了數據
其實除了在shiro的配置文件配置過濾規則,也能夠經過註解的方式在controller上加入權限,效果是同樣的
圖中框起來的地方能夠設置 AND 或者是 OR , 就是設置多個角色的時候, 是所有知足仍是知足一個便可
基於角色的權限設置粒度還比較粗,能夠在細一點,針對每一個功能進行設置,這時候就用到了那張權限表
還用剛纔的作測試,設置兩個權限,咱們在數據庫設置的權限是 ‘book:*’ , 測試發現沒問題能夠請求到
其實除了這種配置方式,還能夠經過註解的方式,配置方式相似角色的設置
這樣權限配置就差很少了,還有種狀況就是要對頁面中按鈕之類的進行權限控制,作法其實也比較簡單
在thymelef 下使用html 進行測試,須要的jar 上面已經導入了,在shiro的config中配置ShiroDialect ,這個上面的配置文件也已經配置好了,剩下的就是在頁面頭部中引入xmlns
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
複製代碼
頁面裏放兩個按鈕,配置不一樣的權限,數據庫中的權限改成"book:list",看一下效果
<tr>
<td colspan="2">
<button shiro:hasPermission="book:list" type="reset">
重置
</button>
<button shiro:hasPermission="book:add" type="button" onclick="submit1()">
提交
</button>
</td>
</tr>
複製代碼
發現只有擁有權限的按鈕才能顯示出來,並且查看頁面源碼發現沒有權限的按鈕根本就沒有生成在頁面中
總結一下,瞭解了上面的這一系列概念和配置,shiro的基本使用應該是沒啥問題的了,接下來在研究一下shiro怎麼作單點登陸的。