Spring Boot -- Apache Shiro
阿新 • • 發佈:2018-11-10
Spring Boot -- Apache Shiro
1. pom.xml
shiro並沒有提供對應的Starter,而是使用的shiro-spring,其它的依賴都是輔助
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</ groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency >
<!-- 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-web</ artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2. application.properties
#資料來源配置
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_test?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#jpa配置
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.show-sql=true
logging.level.com.example.demo.jpa.repository=debug
3. entity
使用者-角色-許可權
一個使用者可以擁有多個角色,同一個角色也可以賦給多個使用者,所以是使用者和角色屬於多對多關係,
一個角色可以有多種許可權,一種許可權屬於多個角色,角色和許可權也屬於多對多關係,多對多需要中間表,多對多使用@ManyToMany標註,中間表使用@JoinTable來標註,使用joinColumns來指定連線列的名稱,使用inverseJoinColumns來指定被連線的列的名稱
使用者表
@Entity
public class UserInfo {
@Id
@GeneratedValue
private Integer uid;
@Column(unique = true)
private String username;
private String password;
private String name;
//用於加密的字串,保證密碼生成的MD5的唯一性
private String salt;
//0:未認證,1:正常狀態,2:使用者被鎖定
private byte state;
//立即從資料庫中進行載入資料
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "uid")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
private List<SysRole> sysRoles;
//get/set
}
角色表
@Entity
public class SysRole {
@Id
@GeneratedValue
private Integer id;
private String role;
private String description;
private Boolean available = Boolean.FALSE;
@ManyToMany
@JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "uid")})
private List<UserInfo> userInfos;
@ManyToMany
@JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "permissionId")})
private List<SysPermission> sysPermissions;
//get/set
}
許可權表
@Entity
public class SysPermission {
@Id
@GeneratedValue
private Integer id;
private String name;
//許可權型別,選單/按鈕
@Column(columnDefinition = "enum('menu','button')")
private String resourceType;
private String url;
//許可權字串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private String permission;
private Integer parentId;
private String parentIds;
private Boolean available = Boolean.FALSE;
@ManyToMany
@JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "permissionId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
private List<SysRole> sysRoles;
//get/set
}
執行應用程式,通過jpa會自動生成表,表名和列名是按照駝峰轉下劃線的風格
- 使用者表: user_info,
- 角色表: sys_role,
- 許可權表: sys_permission,
- 使用者角色中間表: sys_user_role,
- 角色許可權中間表: sys_role_permission
4. 插入測試資料
插入一個admin使用者,密碼123456
插入三個角色:管理員、VIP會員、test
插入三個許可權(資源): 每個資源包含 資源型別(選單或按鈕)、許可權識別符號(一般是模組:操作這種格式)、url地址
角色-許可權:管理員角色中有使用者管理、使用者新增、使用者刪除三個許可權
使用者-角色:admin使用者擁有管理員角色,有使用者管理、使用者新增二個許可權,使用者刪除許可權沒有
INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理員', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理員','admin');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP會員','vip');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'使用者管理',0,'0/','userInfo:view','menu','userInfo/userList');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'使用者新增',1,'0/1','userInfo:add','button','userInfo/userAdd');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'使用者刪除',1,'0/1','userInfo:del','button','userInfo/userDel');
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
5.dao/service
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
@Override
public UserInfo findByUserName(String username) {
return userInfoDao.findByUserName(username);
}
}
6.ShiroConfig
Config
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//設定登入的路徑,如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
//設定登入成功後要跳轉的連結
shiroFilterFactoryBean.setSuccessUrl("/index");
//設定訪問沒有許可權跳轉到的介面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//過濾器鏈,攔截的順序是按照配置的順序來的
//過濾鏈定義,從上向下順序執行
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//配置不會被攔截的路徑,一般靜態資源都不需要攔截,anon代表匿名的不需要攔截的資源,這裡的靜態資源的匹配模式配置成/static/**,
filterChainDefinitionMap.put("/static/**", "anon");
//登出路徑使用logout攔截器
filterChainDefinitionMap.put("/logout", "logout");
//authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//憑證匹配器
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//雜湊演算法:這裡使用MD5演算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//雜湊的次數md5(md5(""))
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
@Bean
public AuthorizingRealm authorizingRealm(){
AuthorizingRealm authorizingRealm = new DemoShiroRealm();
authorizingRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return authorizingRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authorizingRealm());
return securityManager;
}
/**
* 開啟shiro aop註解支援
* 使用代理方式;所以需要開啟程式碼支援
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
//資料庫異常處理
mappings.setProperty("DatabaseException", "databaseError");
mappings.setProperty("UnauthorizedException","403");
simpleMappingExceptionResolver.setExceptionMappings(mappings);
simpleMappingExceptionResolver.setDefaultErrorView("error");
simpleMappingExceptionResolver.setExceptionAttribute("ex");
return simpleMappingExceptionResolver;
}
}
AuthorizingRealm
public class DemoShiroRealm extends AuthorizingRealm {
@Resource
private UserService userService;
/**
* 授權:SimpleAuthorizationInfo用於儲存使用者的所有角色(Set<String> roles)和所有許可權(Set<String> stringPermissions)資訊
* 當執行某個方法時,方法上會有許可權註解,例如@RequiresPermissions("userInfo:add"),
* 此時就會去找AuthorizationInfo中的stringPermissions是否包含userInfo:add,如果包含就繼續處理,
* 如果不包含則跳轉到shiro配置的為授權的地址
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();
for (SysRole role : userInfo.getSysRoles()){
//新增角色
authorizationInfo.addRole(role.getRole());
for (SysPermission permission : role.getSysPermissions()){
//新增許可權
authorizationInfo.addStringPermission(permission.getPermission());
}
}
return authorizationInfo;
}
/**
* 認證
* 主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確
* 當用戶登入時會執行
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
//實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
UserInfo userInfo = userService.findByUserName(username);
if(userInfo == null){
return null;
}
//userInfo.getCredentialsSalt()鹽的生成方式 這裡使用salt=username+salt
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getCredentialsSalt()), getName());
return authenticationInfo;
}
}
7.controller
HomeController
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
/**
* 登入時先執行Realm中的認證方法,然後再執行登入方法
* @param request
* @return
*/
@RequestMapping("/login")
public String login(HttpServletRequest request){
//登入失敗從request中獲取shiro處理的異常資訊
//shiroLoginFailure:就是shiro異常類的全類名
String exception = (String) request.getAttribute("shiroLoginFailure");
String msg = "";
if(exception != null){
if(UnknownAccountException.class.getName().equals(exception)){
System.out.println("UnknownAccountException -- > 賬號不存在:");
msg = "UnknownAccountException -- > 賬號不存在:";
} else if (IncorrectCredentialsException.class.getName().equals(exception)){
System.out.println("IncorrectCredentialsException -- > 密碼不正確:");
msg = "IncorrectCredentialsException -- > 密碼不正確:";
} else if ("kaptchaValidateFailed".equals(exception)){
System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤");
msg = "kaptchaValidateFailed -- > 驗證碼錯誤";
}else {
msg = "else >> "+exception;
System.out.println("else -- >" + exception);
}
}
request.setAttribute("msg", msg);
// 此方法不處理登入成功,由shiro進行處理, 應為會在shiro中配置登入成功需要跳轉的介面
return "/login";
}
@RequestMapping("/403")
public String unauthorizedRole(){
return "403";
}
}
UserInfoController
@RequestMapping("/userInfo")
@Controller
public class UserInfoController {
/**
* 使用者查詢
* 檢視使用者資訊的許可權
* @return
*/
@RequiresPermissions("userInfo:view")
@RequestMapping("/userList")
public String userInfo(){
return "userInfo";
}
/**
* 使用者新增
* 新增使用者的許可權
* @return
*/