1、Shiro是Apache下的一個開源項目,我們稱之為Apache Shiro。它是一個很易用與Java項目的的安全框架,提供了認證、授權、加密、會話管理,與spring Security 一樣都是做一個權限的安全框架,但是與Spring Security 相比,在於 Shiro 使用了比較簡單易懂易於使用的授權方式。shiro屬於輕量級框架,相對於security簡單的多,也沒有security那麼複雜。所以我這裡也是簡單介紹一下shiro的使用。
2、非常簡單;其基本功能點如下圖所示:
Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
Session Manager:會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的;
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
Web Support:Web支持,可以非常容易的集成到Web環境;
Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;
Concurrency:shiro支持多線程應用的併發驗證,即如在一個線程中開啟另一個線程,能把權限自動傳播過去;
Testing:提供測試支持;
Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄了。
記住一點,Shiro不會去維護用戶、維護權限;這些需要我們自己去設計/提供;然後通過相應的接口注入給Shiro即可。
3、這裡我就簡單介紹一下springboot和shiro整合與基本使用。
需要的基礎包:pom.xml
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.winter
HtmlCssJs
0.0.1-SNAPSHOT
war
HtmlCssJs
This is Html Css Js Demo Project
org.springframework.boot
spring-boot-starter-parent
1.5.3.RELEASE
UTF-8
UTF-8
1.8
net.sourceforge.nekohtml
nekohtml
net.sf.json-lib
json-lib
2.4
jdk15
org.apache.shiro
shiro-spring
1.4.0
org.quartz-scheduler
quartz
2.2.1
org.springframework
spring-context-support
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.apache.tomcat
tomcat-servlet-api
8.0.36
provided
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-mail
mysql
mysql-connector-java
javax.servlet
javax.servlet-api
javax.servlet
jstl
org.apache.tomcat
tomcat-jsp-api
org.apache.tomcat.embed
tomcat-embed-core
org.apache.tomcat.embed
tomcat-embed-jasper
com.fasterxml.jackson.core
jackson-core
com.fasterxml.jackson.core
jackson-databind
com.fasterxml.jackson.datatype
jackson-datatype-joda
com.fasterxml.jackson.module
jackson-module-parameter-names
com.github.pagehelper
pagehelper-spring-boot-starter
1.1.2
com.alibaba
druid-spring-boot-starter
1.1.0
redis.clients
jedis
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.data
spring-data-redis
aspectj
aspectjweaver
1.5.3
org.weixin4j.spring.boot
weixin4j-spring-boot-starter
1.0.0
com.soecode.wx-tools
wx-tools
2.1.4-RELEASE
org.springframework.boot
spring-boot-maven-plugin
基本配置application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
mybatis.type-aliases-package=com.cwh.springbootMybatis.entity
#thymeleaf模板使用
#關閉thymeleaf緩存
spring.thymeleaf.cache=false
#去掉thymeleaf的嚴格的模板校驗
#spring.thymeleaf.mode=LEGACYHTML5
#Spring Jpa
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true
# Redis數據庫索引(默認為0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=127.0.0.1
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認為空)
spring.redis.password=
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.pool.max-wait=-1
# 連接池中的最大空閒連接
spring.redis.pool.max-idle=8
# 連接池中的最小空閒連接
spring.redis.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=0
OK ,開始我們需要3個實體,用戶,角色和權限
// 實體類名
@Entity
// 映射數據庫表名
@Table(name="tb_user")
public class User implements Serializable{
/**
*/
private static final long serialVersionUID = 1L;
@Id
// 主鍵自增長
@GeneratedValue(strategy=GenerationType.AUTO)
public Long id;
// 列名
@Column
public String name;
@Column
public String password;
@OneToMany(cascade=CascadeType.ALL)
private List roles;
// 省略get set..
@Entity
@Table(name="tb_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String roleName;
@ManyToOne(fetch = FetchType.EAGER)
private User user;
@OneToMany(cascade = CascadeType.ALL)
private List permissions;
// 省略get set..
@Entity
@Table(name="tb_permission")
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String permission;
@ManyToOne(fetch = FetchType.EAGER)
private Role role;
// 省略get set..
重啟服務,對應看數據庫,已自動生成如下表:
注入Shiro Factory和SecurityManager:
// shiro核心配置類
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不設置值,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/nologin");
// 設置無權限時跳轉的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 默認成功界面,登陸成功後 跳轉
shiroFilterFactoryBean.setSuccessUrl("/index");
// 設置攔截器
Map<string> filterChainDefinitionMap = new LinkedHashMap<>();/<string>
//遊客,開發權限
filterChainDefinitionMap.put("/guest/**", "anon");
//用戶,需要角色權限 “user”
filterChainDefinitionMap.put("/user/**", "roles[user]");
//管理員,需要角色權限 “admin”
filterChainDefinitionMap.put("/admin/**", "roles[admin]");
//開放登陸接口
filterChainDefinitionMap.put("/login", "anon");
//其餘接口一律攔截
//主要這行代碼必須放在所有權限設置的最後,不然會導致所有 url 都被攔截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro攔截器工廠類注入成功");
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設置realm.
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定義身份認證 realm;
*
* 必須寫這個類,並加上 @Bean 註解,目的是注入 CustomRealm,
* 否則會影響 CustomRealm類 中其他類的依賴注入
*/
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
}
注意:SecurityManager這有點坑,默認是導入java.lang.SecurityManager包 可以寫成我這種格式,即使不太美觀
org.apache.shiro.mgt.SecurityManager
這裡說下:ShiroFilterFactory中已經由Shiro官方實現的過濾器
anon:所有url都都可以匿名訪問;
authc: 需要認證才能進行訪問;
user:配置記住我或認證通過可以訪問
身份認證
在認證、授權內部實現機制中都有提到,最終處理都將交給Real進行處理。因為在Shiro中,最終是通過Realm來獲取應用程序中的用戶、角色及權限信息的。通常情況下,在Realm中會直接從我們的數據源中獲取Shiro需要的驗證信息。可以說,Realm是專用於安全框架的DAO.
認證實現
public class CustomRealm extends AuthorizingRealm{
private UserRepository userMapper;
@Autowired
private void setUserMapper(UserRepository userMapper) {
this.userMapper = userMapper;
}
/**
* 獲取身份驗證信息
* Shiro中,最終是通過 Realm 來獲取應用程序中的用戶、角色及權限信息的。
*
* @param authenticationToken 用戶身份信息 token
* @return 返回封裝了用戶信息的 AuthenticationInfo 實例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("————身份認證方法————");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 從數據庫獲取對應用戶名密碼的用戶
User user = userMapper.findPasswordByName(token.getUsername());
if (null == user.getPassword()) {
throw new AccountException("用戶名不正確");
} else if (!user.getPassword().equals(new String((char[]) token.getCredentials()))) {
throw new AccountException("密碼不正確");
}
return new SimpleAuthenticationInfo(token.getPrincipal(), user.getPassword(), getName());
}
/**
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("————權限認證————");
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//獲得該用戶角色
User user = userMapper.findRoleByName(username);
Set set = new HashSet<>();
List roles = user.getRoles();
for (Role role2 : roles) {
String roleName = role2.getRoleName();
//需要將 role 封裝到 Set 作為 info.setRoles() 的參數
set.add(roleName);
}
//設置該用戶擁有的角色
info.setRoles(set);
return info;
}
}
然後在控制器層;
@Controller
public class UserController {
@Autowired
private IUserService userService;
// 注:此處不能使用@ResponseBody 因為使用過後,控制層就當字符串返回了
// 創建Model對象,將信息以Key,Value形式放置Model域中
@RequestMapping("/findOne")
public String findOne(Long id,Model model){
User user = userService.findOne(id);
model.addAttribute("user", user);
return "/user/findOne";
}
@RequestMapping("/nologin")
public String nologin(){
return "/user/login";
}
/**
* 登陸
*
* @param username 用戶名
* @param password 密碼
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public String login(String username, String password) {
// 從SecurityUtils裡邊創建一個 subject
Subject subject = SecurityUtils.getSubject();
// 在認證提交前準備 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 執行認證登陸
subject.login(token);
// 根據權限,指定返回數據
List role = userService.findRoleByName(username).getRoles();
for (Role role2 : role) {
if ("user".equals(role2.getRoleName())) {
return "歡迎登陸";
}
if ("admin".equals(role2.getRoleName())) {
return "歡迎來到管理員頁面";
}
}
return "權限錯誤!";
}
@RequestMapping("/index")
public String index(Model model){
model.addAttribute("msg", "歡迎訪問主頁");
return "/user/index";
}
}
省掉了登陸界面等
進行訪問,我們直接在地址欄訪問主頁。
192.168.40.88:8080/index會跳轉到登陸界面,因為我們還未登陸
登陸後,
閱讀更多 JAVA君 的文章