Shiro是Apache旗下的一個開源項目,它是一個很是易用的安全框架,提供了包括認證、受權、加密、會話管理等功能,與Spring Security同樣屬基於權限的安全框架,可是與Spring Security 相比,Shiro使用了比較簡單易懂易於使用的受權方式。Shiro屬於輕量級框架,相對於Spring Security簡單不少,並無security那麼複雜。html
它是一個功能強大、靈活的、優秀的、開源的安全框架。java
它能夠勝任身份驗證、受權、企業會話管理和加密等工做。mysql
它易於使用和理解,與Spring Security相比,入門門檻低。git
Shiro 的總體框架大體以下圖所示(圖片來自互聯網):web
Authentication(認證), Authorization(受權), Session Management(會話管理), Cryptography(加密)表明Shiro應用安全的四大基石。算法
它們分別是:spring
除此以外,還有其餘的功能來支持和增強這些不一樣應用環境下安全領域的關注點。sql
特別是對如下的功能支持:數據庫
在概念層,Shiro 架構包含三個主要的理念:Subject, SecurityManager 和 Realm。下面的圖展現了這些組件如何相互做用,咱們將在下面依次對其進行描述。apache
Shiro執行流程圖(圖片來自互聯網)
三個主要理念:
咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。
接下來,咱們就經過一個具體的案例,來說解如何進行Shiro的整合,而後藉助Shiro實現登陸認證和訪問控制。
爲方便咱們初始化項目,Spring Boot給咱們提供一個項目模板生成網站。
1. 打開瀏覽器,訪問:https://start.spring.io/
2. 根據頁面提示,選擇構建工具,開發語言,項目信息等。
3. 點擊 Generate the project,生成項目模板,生成以後會將壓縮包下載到本地。
4. 使用IDE導入項目,我這裏使用Eclipse,經過導入Maven項目的方式導入。
清理掉不須要的測試類及測試依賴,添加 Maven 相關依賴,這裏須要添加上WEB、Swagger、JPA和Shiro的依賴,Swagger的添加是爲了方便接口測試。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.louis.springboot</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!-- jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <!-- 打包時拷貝MyBatis的映射文件 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/sqlmap/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> <filtering>true</filtering> </resource> </resources> </build> </project>
1.添加數據源和jpa相關配置
將application.properties文件更名爲application.yml ,並在其中添加MySQL數據源鏈接信息。
注意:
這裏須要首先建立一個MySQL數據庫,並輸入本身的用戶名和密碼。這裏的數據庫是springboot。
另外,若是你使用的是MySQL 5.x及之前版本,驅動配置driverClassName是com.mysql.jdbc.Driver。
application.yml
server: port: 8080 spring: datasource: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8 username: root password: 123456 jpa: show-sql: true # 默認false,在日誌裏顯示執行的sql語句 database: mysql hibernate.ddl-auto: update #指定爲update,每次啓動項目檢測表結構有變化的時候會新增字段,表不存在時會新建,若是指定create,則每次啓動項目都會清空數據並刪除表,再新建 properties.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect database-platform: org.hibernate.dialect.MySQL5Dialect hibernate: naming: implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl #指定jpa的自動錶生成策略,駝峯自動映射爲下劃線格式 #physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
2. 添加swagger 配置
添加一個swagger 配置類,在工程下新建 config 包並添加一個 SwaggerConfig 配置類。
SwaggerConfig.java
package com.louis.springboot.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()).build(); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("SpringBoot API Doc") .description("This is a restful api document of Spring Boot.") .version("1.0") .build(); } }
添加一個用戶類User,包含用戶名和密碼,用來進行登陸認證,另外用戶能夠擁有角色。
User.java
package com.louis.springboot.demo.model; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(unique = true) private String name; private String password; @OneToMany(cascade = CascadeType.ALL,mappedBy = "user") private List<Role> roles; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
添加一個角色類Role,表示用戶角色,角色擁有可操做的權限集合。
Role.java
package com.louis.springboot.demo.model; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; @Entity 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,mappedBy = "role") private List<Permission> permissions; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public List<Permission> getPermissions() { return permissions; } public void setPermissions(List<Permission> permissions) { this.permissions = permissions; } }
添加一個權限類Permission,表示資源訪問權限。
Permission.java
package com.louis.springboot.demo.model; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; @Entity public class Permission { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String permission; @ManyToOne(fetch = FetchType.EAGER) private Role role; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
添加一個DAO基礎接口,用來被其餘DAO繼承。
BaseDao.java
package com.louis.springboot.demo.dao; import java.io.Serializable; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.PagingAndSortingRepository; @NoRepositoryBean public interface BaseDao<T, I extends Serializable> extends PagingAndSortingRepository<T, I>, JpaSpecificationExecutor<T> { }
添加一個UserDao,用來操做用戶信息。
UserDao.java
package com.louis.springboot.demo.dao; import com.louis.springboot.demo.model.User; public interface UserDao extends BaseDao<User, Long> { User findByName(String name); }
添加一個RoleDao,用來操做角色信息。
RoleDao.java
package com.louis.springboot.demo.dao; import com.louis.springboot.demo.model.Role; public interface RoleDao extends BaseDao<Role, Long> { }
添加一個LoginService服務接口。
LoginService.java
package com.louis.springboot.demo.service; import com.louis.springboot.demo.model.Role; import com.louis.springboot.demo.model.User; public interface LoginService { User addUser(User user); Role addRole(Role role); User findByName(String name); }
添加一個LoginServiceImpl,實現服務功能,這裏爲了方便,在插入角色的時候會默認設置其權限。
LoginServiceImpl.java
package com.louis.springboot.demo.service.impl; import java.util.ArrayList; import java.util.List; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.louis.springboot.demo.dao.RoleDao; import com.louis.springboot.demo.dao.UserDao; import com.louis.springboot.demo.model.Permission; import com.louis.springboot.demo.model.Role; import com.louis.springboot.demo.model.User; import com.louis.springboot.demo.service.LoginService; @Service @Transactional public class LoginServiceImpl implements LoginService { @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; //添加用戶 @Override public User addUser(User user) { userDao.save(user); return user; } //添加角色 @Override public Role addRole(Role role) { User user = userDao.findByName(role.getUser().getName()); role.setUser(user); Permission permission1 = new Permission(); permission1.setPermission("create"); permission1.setRole(role); Permission permission2 = new Permission(); permission2.setPermission("update"); permission2.setRole(role); List<Permission> permissions = new ArrayList<Permission>(); permissions.add(permission1); permissions.add(permission2); role.setPermissions(permissions); roleDao.save(role); return role; } //查詢用戶經過用戶名 @Override public User findByName(String name) { return userDao.findByName(name); } }
添加一個登陸控制器,編寫相關的接口。create接口添加了@RequiresPermissions("create"),用於進行權限註解測試。
LoginController.java
package com.louis.springboot.demo.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.louis.springboot.demo.model.Role; import com.louis.springboot.demo.model.User; import com.louis.springboot.demo.service.LoginService; @RestController public class LoginController { @Autowired private LoginService loginService; /** * POST登陸 * @param map * @return */ @PostMapping(value = "/login") public String login(@RequestBody User user) { // 添加用戶認證信息 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getName(), user.getPassword()); // 進行驗證,這裏能夠捕獲異常,而後返回對應信息 SecurityUtils.getSubject().login(usernamePasswordToken); return "login ok!"; } /** * 添加用戶 * @param user * @return */ @PostMapping(value = "/addUser") public String addUser(@RequestBody User user) { user = loginService.addUser(user); return "addUser is ok! \n" + user; } /** * 添加角色 * @param role * @return */ @PostMapping(value = "/addRole") public String addRole(@RequestBody Role role) { role = loginService.addRole(role); return "addRole is ok! \n" + role; } /** * 註解的使用 * @return */ @RequiresRoles("admin") @RequiresPermissions("create") @GetMapping(value = "/create") public String create() { return "Create success!"; } @GetMapping(value = "/index") public String index() { return "index page!"; } @GetMapping(value = "/error") public String error() { return "error page!"; } /** * 退出的時候是get請求,主要是用於退出 * @return */ @GetMapping(value = "/login") public String login() { return "login"; } @GetMapping(value = "/logout") public String logout() { return "logout"; } }
添加一個MyShiroRealm並繼承AuthorizingRealm,實現其中的兩個方法。
doGetAuthenticationInfo:實現用戶認證,經過服務加載用戶信息並構造認證對象返回。
doGetAuthorizationInfo:實現權限認證,經過服務加載用戶角色和權限信息設置進去。
MyShiroRealm.java
package com.louis.springboot.demo.config; 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.springframework.beans.factory.annotation.Autowired; import com.louis.springboot.demo.model.Permission; import com.louis.springboot.demo.model.Role; import com.louis.springboot.demo.model.User; import com.louis.springboot.demo.service.LoginService; /** * 實現AuthorizingRealm接口用戶用戶認證 * @author Louis * @date Jun 20, 2019 */ public class MyShiroRealm extends AuthorizingRealm { @Autowired private LoginService loginService; /** * 用戶認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 加這一步的目的是在Post請求的時候會先進認證,而後在到請求 if (authenticationToken.getPrincipal() == null) { return null; } // 獲取用戶信息 String name = authenticationToken.getPrincipal().toString(); User user = loginService.findByName(name); if (user == null) { // 這裏返回後會報出對應異常 return null; } else { // 這裏驗證authenticationToken和simpleAuthenticationInfo的信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName()); return simpleAuthenticationInfo; } } /** * 角色權限和對應權限添加 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 獲取登陸用戶名 String name = (String) principalCollection.getPrimaryPrincipal(); // 查詢用戶名稱 User user = loginService.findByName(name); // 添加角色和權限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (Role role : user.getRoles()) { // 添加角色 simpleAuthorizationInfo.addRole(role.getRoleName()); for (Permission permission : role.getPermissions()) { // 添加權限 simpleAuthorizationInfo.addStringPermission(permission.getPermission()); } } return simpleAuthorizationInfo; } }
添加一個Shiro配置類,主要配置路由的訪問控制,以及注入自定義的認證器MyShiroRealm。
ShiroConfig.java
package com.louis.springboot.demo.config; import java.util.HashMap; import java.util.Map; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ShiroConfig { // 將本身的驗證方式加入容器 @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } // 權限管理,配置主要是Realm的管理認證 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } // Filter工廠,設置對應的過濾條件和跳轉條件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterMap = new HashMap<String, String>(); // 登出 filterMap.put("/logout", "logout"); // swagger filterMap.put("/swagger**/**", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/v2/**", "anon"); // 對全部用戶認證 filterMap.put("/**", "authc"); // 登陸 shiroFilterFactoryBean.setLoginUrl("/login"); // 首頁 shiroFilterFactoryBean.setSuccessUrl("/index"); // 錯誤頁面,認證不經過跳轉 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } // 加入註解的使用,不加入這個註解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
1. 右鍵項目 -> Run as -> Maven install,開始執行Maven構建,第一次會下載Maven依賴,可能須要點時間,若是出現以下信息,就說明項目編譯打包成功了。
2. 右鍵文件 DemoApplication.java -> Run as -> Java Application,開始啓動應用,若是一開始數據庫沒有對應的表,在應用啓動時會建立,咱們能夠經過控制檯查看到對應的SQL語句。
3. 打開瀏覽器,訪問:http://localhost:8080/swagger-ui.html,進入swagger接口文檔界面。
4. 首先用MySQL客戶端,打開數據庫,往用戶表裏面插入一條記錄,{id=1, name="admin", password="123"}。
而後試着用Swagger調用addUser往用戶表插入一條記錄。
{ "id": 2, "name": "xiaoming", "password": "123" }
結果返回"error page!",這是由於咱們沒有登陸,尚未操做權限。
接着調用POST的login接口,輸入如下用戶信息進行登陸。
{ "name": "admin", "password": "123" }
登陸成功以後,返回「login ok!」信息。
再次調用addUser往用戶表插入記錄,發現記錄已經能夠成功插入了。
經過客戶端工具咱們也能夠查看到記錄已經插入進來了。
經過上面的測試,咱們已經成功的驗證了,受保護的接口須要在登陸以後才容許訪問。接下來咱們來測試一下權限註解的效果,咱們在create方法上加上了權限註解@RequiresPermissions("create"),表示用戶須要擁有"create"的權限才能訪問。
先嚐試調用如下create接口,發現儘管咱們已經登陸了,依然由於沒有權限返回了「error page!」。
而後咱們調用addRole插入如下角色記錄,這個角色關聯了咱們當前登陸admin用戶,且角色在建立時咱們代碼默認設置擁有了「create」權限。
{ "id": 1, "roleName": "admin", "user": { "name": "admin" } }
若是執行正確的話,會返回以下信息,說明角色已經成功插入了。
而後咱們再一次調用create接口,由於此刻admin用戶擁有admin角色,而admin角色擁有「create」權限,因此已經具備接口訪問權限了。
W3C資料:https://www.w3cschool.cn/shiro/
百度百科:https://baike.baidu.com/item/shiro/17753571?fr=aladdin
碼雲:https://gitee.com/liuge1988/spring-boot-demo.git
做者:朝雨憶輕塵
出處:https://www.cnblogs.com/xifengxiaoma/
版權全部,歡迎轉載,轉載請註明原文做者及出處。