使用shiro實現使用者登入認證和簡單許可權的實現(法院專案)
阿新 • • 發佈:2018-12-19
實現登入認證
步驟一:匯入依賴包
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27-SNAPSHOT</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> </dependencies>
步驟二:構建配置檔案類ShiroConfig
package com.qf.fayuan.config; import com.qf.fayuan.bean.MyShiroFactoryBean; import com.qf.fayuan.mapper.PermissonMapper; import com.qf.fayuan.mapper.UserMapper; import com.qf.fayuan.shiro.realm.MyRealm; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; @Configuration public class ShiroConfig { @Autowired private UserMapper userMapper; @Autowired private PermissonMapper permissonMapper; @Bean public DefaultWebSecurityManager securityManager(MyRealm myRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); return securityManager; } @Bean public MyRealm myRealm(CredentialsMatcher credentialsMatcher, UserMapper userMapper,PermissonMapper permissonMapper){ //需要另外編寫MyRealm類 MyRealm myRealm = new MyRealm(); myRealm.setUserMapper(userMapper); myRealm.setCredentialsMatcher(credentialsMatcher); myRealm.setPermissonMapper(permissonMapper); return myRealm; } @Bean public CredentialsMatcher credentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashIterations(1024); hashedCredentialsMatcher.setHashAlgorithmName("MD5"); return hashedCredentialsMatcher; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public FilterRegistrationBean delegatingFilterProxy() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy("shiroFilter"); filterRegistrationBean.setFilter(delegatingFilterProxy); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.setEnabled(true); filterRegistrationBean.addInitParameter("targetFilterLifecycle", "true"); return filterRegistrationBean; } @Bean public MyShiroFactoryBean shiroFilter(SecurityManager securityManager, PermissonMapper permissonMapper) { //這個類也要另建 MyShiroFactoryBean myShiroFactoryBean = new MyShiroFactoryBean(); myShiroFactoryBean.setPermissonMapper(permissonMapper); myShiroFactoryBean.setSecurityManager(securityManager); return myShiroFactoryBean; } }
第三步:建立MyRealm類
package com.qf.fayuan.shiro.realm; import com.qf.fayuan.mapper.PermissonMapper; import com.qf.fayuan.mapper.UserMapper; import com.qf.fayuan.shiro.pojo.CourtAdmin; import com.qf.fayuan.shiro.usernamepasswordtoken.UserNamePassWordToken; import com.qf.fayuan.user.pojo.User; import com.qf.fayuan.vo.UserVo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; 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.apache.shiro.util.ByteSource; import java.util.List; public class MyRealm extends AuthorizingRealm { private UserMapper userMapper; private PermissonMapper permissonMapper; public void setPermissonMapper(PermissonMapper permissonMapper) { this.permissonMapper = permissonMapper; } public void setUserMapper(UserMapper userMapper) { this.userMapper = userMapper; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //獲取使用者的使用者名稱 String username = ((UserNamePassWordToken) authenticationToken).getUsername(); //獲取使用者的角色 int role = ((UserNamePassWordToken) authenticationToken).getUser_role(); //根據使用者名稱和使用者角色,從資料庫中查詢該使用者的資訊 CourtAdmin courtAdmin = userMapper.getCourtAdmin(username,role); //將使用者存在資料庫中的鹽取出並且變成位元組型別 ByteSource bytes = ByteSource.Util.bytes(courtAdmin.getSalt()); //傳入使用者名稱,使用者在資料庫中真實的的密碼,鹽,getName(); SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,courtAdmin.getPassword(),bytes,getName()); //返回帶有使用者資訊的物件 return simpleAuthenticationInfo; } }
步驟四:編寫contorller層,實現使用者的登入驗證
// @LogAnno(operationtype = "/login",operationname = "使用者登陸,伺服器返回使用者的部分資訊")
@RequestMapping("/login")// @RequestParam(value = "user_role",required = false,defaultValue = "1")
public ResultBean login(String userinfo, Integer user_role, String password){
//獲取當前登入使用者的連結
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()){
//將使用者的使用者名稱和密碼存入到UserNamePassWordToken中
UserNamePassWordToken usernamePasswordToken = new UserNamePassWordToken(userinfo,password,user_role);
try{
//驗證登入使用者的資訊是否正確,如果失敗的話就會報錯處理,所以需要捕獲異常
subject.login(usernamePasswordToken);
}catch (Exception e){
e.printStackTrace();
return ResultBean.setError(ErrorCodeInterface.MIMACUOWU,"資訊錯誤",null);
}
}
UserVo userVo = userService.login(userinfo,user_role);
SecurityUtils.getSubject().getSession().setAttribute("user",userVo);
return ResultBean.setOk(userVo);
}
在上面中發現使用到了UserNamePassWordToken 這個物件,這個物件中可以攜帶幾個欄位,如username和password等,這個時候我想讓這個物件也要攜帶我自定義的一個欄位role,那麼需要自定義一個類,讓這個類去繼承UserNamePassWordToken,然後新增一個新的欄位role
類的內容如下:
package com.qf.fayuan.shiro.usernamepasswordtoken;
import org.apache.shiro.authc.UsernamePasswordToken;
public class UserNamePassWordToken extends UsernamePasswordToken {
private int user_role;
public UserNamePassWordToken(String username,String password,int user_role){
super(username,password);
this.user_role = user_role;
}
public int getUser_role() {
return user_role;
}
public void setUser_role(int user_role) {
this.user_role = user_role;
}
}
根據上面的內容就可以使用shiro實現簡單的登入認證功能了。
實現許可權部分
步驟一:首先定義一個自定義註解,註解中包含,訪問需要的許可權(設定該路徑名就是需要訪問的許可權) 、許可權表述的相關資訊
package com.qf.fayuan.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissonAnno {
String value();//許可權的值,將方法或者類的路徑設定為所要許可權的值
String comment();//許可權的描述 ,該許可權的描述不應為空或者重複
boolean isMenu() default false;//判斷是否是一個表單
String parent(); //其父類的許可權描述
}
步驟二:將註解新增到類和方法的上面
@PermissonAnno(value = "/getnoticeinfo",comment="查詢公告",isMenu = false,parent = "公告列表")
步驟三:遍歷每一個類和類中的每一個方法,將許可權新增到表中 首先定義一個工具類來封裝註解中的資訊
package com.qf.fayuan.shiro.pojo;
public class PermissonBean {
private int id;
private String value;
private String comment;
private boolean isMenu;
private String parent;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public boolean isMenu() {
return isMenu;
}
public void setMenu(boolean menu) {
isMenu = menu;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
@Override
public String toString() {
return "PermissonBean{" +
"id=" + id +
", value='" + value + '\'' +
", comment='" + comment + '\'' +
", isMenu=" + isMenu +
", parent='" + parent + '\'' +
'}';
}
}
然後通過給定一個專案的路徑,就可以掃描這個專案中的所以類和方法
package com.qf.fayuan.utils;
import com.qf.fayuan.anno.PermissonAnno;
import com.qf.fayuan.shiro.pojo.PermissonBean;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class PermissonAnnoUtils {
// 定義一個set集合用來儲存判斷是含有該註解的類
private static Set<Class> classSet = new HashSet<>();
//將直接定義在類上面的註解的資訊封裝到物件,然後存入到該集合中
private static Set<PermissonBean> classBean = new LinkedHashSet<>();
//將定義在方法上的註解的資訊封裝到物件中然後存入到該集合中
private static Set<PermissonBean> methodBean = new LinkedHashSet<>();
public static void getClassSet(String packagename) throws Exception {
/**
* xxx.class.getResource()用來從當前類(xxx)所在的目錄下(也就是以當前類所在路徑為根路徑)獲得資源;
* xxx.class.getClassLoader().getResource()用來從classpath路徑下(也就是以classpath所在路徑為根路徑)獲得資源。
*/
//根據專案中一段路徑(com.qf.fayuan),獲得 file:/D:/idea/idea-workspace/fayuanxiangmu/target/classes/com/qf/fayuan 其中file為協議
URL resource = PermissonAnnoUtils.class.getClassLoader().getResource(packagename.replace(".", "/"));
if(resource!=null||"file".equalsIgnoreCase(resource.getProtocol())){
//getProtocol獲取協議:也就是 file: 部分
String packagepath = URLDecoder.decode(resource.getFile(),"utf-8");
///D:/idea/idea-workspace/fayuanxiangmu/target/classes/com/qf/fayuan
addClass(packagename,packagepath);
}
}
private static void addClass(String packagename, String packagepath) throws Exception {
//獲得該路徑下的所有檔案,並且新增一個過濾器,設定條件
File[] files = new File(packagepath).listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
//如果是一個檔案或者是一個類,或者是一個目錄,就遍歷進入陣列中
return (pathname.isFile() || pathname.getName().endsWith(".class") || pathname.isDirectory());
}
});
for (File file : files) {
//file的格式為: D:\idea\idea-workspace\fayuanxiangmu\target\classes\com\qf\fayuan\anno
//獲得檔案的名字
String name = file.getName();
//name 的值是 anno
if(file.isFile()){
if(!name.contains("Controller")){
continue;
}
String classname = name.substring(0,name.lastIndexOf("."));
classname = packagename + "." +classname;
doAddClass(classname);
}else {
String subpackagepath = name;
if(!StringUtils.EmptyString(packagepath)){
subpackagepath = packagepath+"/"+name;
}
String subpackagename = name;
if(!StringUtils.EmptyString(packagename)){
subpackagename = packagename+"."+name;
}
addClass(subpackagename,subpackagepath);
}
}
}
private static void doAddClass(String classname) throws ClassNotFoundException {
Class<?> aClass = Class.forName(classname);
classSet.add(aClass);
}
public static void inject(String packagename) throws Exception {
classSet.clear();
classBean.clear();
methodBean.clear();
getClassSet(packagename);
for (Class clazz : classSet) {
//掃描類上的註解
PermissonAnno annotation = (PermissonAnno) clazz.getAnnotation(PermissonAnno.class);
String value = null;
String comment = null;
boolean isMenu = false;
String parent = null;
String parentValue = null;
if(annotation!=null){
comment = annotation.comment();
parentValue = annotation.value();
isMenu = annotation.isMenu();
parent = annotation.parent();
PermissonBean permissonBean = new PermissonBean();
permissonBean.setComment(comment);
permissonBean.setValue(parentValue);
permissonBean.setMenu(isMenu);
permissonBean.setParent(parent);
classBean.add(permissonBean);
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//掃描方法上的註解
PermissonAnno annotation1 = method.getAnnotation(PermissonAnno.class);
if(annotation1!=null){
value = annotation1.value();
comment = annotation1.comment();
isMenu = annotation1.isMenu();
parent = annotation1.parent();
value = parentValue+value;
PermissonBean permissonBean = new PermissonBean();
permissonBean.setValue(value);
permissonBean.setComment(comment);
permissonBean.setMenu(isMenu);
permissonBean.setParent(parent);
methodBean.add(permissonBean);
}
}
}
}
public static Set<Class> getClassSet() {
return classSet;
}
public static Set<PermissonBean> getClassBean() {
return classBean;
}
public static Set<PermissonBean> getMethodBean() {
return methodBean;
}
public static void main(String[] args) {
File[] files = new File("D:/idea/idea-workspace/fayuanxiangmu/target/classes/com/qf/fayuan").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
System.out.println(pathname);//D:\idea\idea-workspace\fayuanxiangmu\target\classes\com\qf\fayuan\anno
System.out.println(pathname.getName());//anno
return (pathname.isFile() || pathname.getName().endsWith(".class") || pathname.isDirectory());
}
});
for (File file : files) {
System.out.println(file.getName());
}
//
// URL resource = PermissonAnnoUtils.class.getClassLoader().getResource("com.qf.fayuan".replace(".", "/"));
// System.out.println(resource);//file:/D:/idea/idea-workspace/fayuanxiangmu/target/classes/com/qf/fayuan
//
// URL resource1 = PermissonAnnoUtils.class.getResource("com.qf.fayuan");
// System.out.println(resource1);//null
//
// String file = PermissonAnnoUtils.class.getClassLoader().getResource("com.qf.fayuan".replace(".", "/")).getFile();
// System.out.println(file);///D:/idea/idea-workspace/fayuanxiangmu/target/classes/com/qf/fayuan
}
}
別寫controller層,訪問該層,將所有的註解中的資訊存入到資料庫中
package com.qf.fayuan.shiro.controller;
import com.qf.fayuan.anno.PermissonAnno;
import com.qf.fayuan.bean.ResultBean;
import com.qf.fayuan.mapper.PermissonMapper;
import com.qf.fayuan.shiro.pojo.PermissonBean;
import com.qf.fayuan.utils.ErrorCodeInterface;
import com.qf.fayuan.utils.PermissonAnnoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Set;
@RequestMapping("/permisson")
@RestController()
public class PermissonController {
@Autowired
private PermissonMapper permissonMapper;
@RequestMapping("/addallpermisson")
public ResultBean addAllPermisson(){
try {
PermissonAnnoUtils.inject("com.qf.fayuan");
Set<PermissonBean> classBean = PermissonAnnoUtils.getClassBean();
permissonMapper.addAllPermisson(classBean);
Set<PermissonBean> methodBean = PermissonAnnoUtils.getMethodBean();
permissonMapper.addAllPermisson(methodBean);
} catch (Exception e) {
e.printStackTrace();
return ResultBean.setError(ErrorCodeInterface.XIUGAIMIMASHIBAI,"失敗了",null);
}
return ResultBean.setOk(null);
}
}
步驟四:上面將所有的許可權新增到了資料庫中,下面就是將訪問各個類或者方法的許可權新增到shiro中
在MyShiroFactoryBean中新增程式碼即可
package com.qf.fayuan.bean;
import com.qf.fayuan.mapper.PermissonMapper;
import com.qf.fayuan.shiro.pojo.PermissonValue;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import javax.annotation.PostConstruct;
import java.util.LinkedHashMap;
import java.util.List;
public class MyShiroFactoryBean extends ShiroFilterFactoryBean {
//這裡不應該使用自動注入的方式
private PermissonMapper permissonMapper;
public void setPermissonMapper(PermissonMapper permissonMapper) {
this.permissonMapper = permissonMapper;
}
//新增這個註解以為著
@PostConstruct
public void init(){
//設定登入的介面的位置
setLoginUrl("/login.html");
//建立一個map集合,將要訪問的路徑和所需要的許可權新增進去
LinkedHashMap<String, String> perms = new LinkedHashMap<>();
//訪問首頁,需要的許可權是任何許可權都可以訪問
perms.put("/index.html","anon");
perms.put("/login.html","anon");
//如果想訪問success.html頁面,必須進行登入認證,而且會自動跳轉到登入介面
perms.put("/success.html","authc");
//將本專案的所有許可權新增到新增到shiro中
List<PermissonValue> list = permissonMapper.getAllPermisson();
if(list!=null){
for (PermissonValue value : list) {
//注意新增許可權的時候,許可權的書寫格式
perms.put(value.getValue(),"perms["+value.getValue()+"]");
}
}
setFilterChainDefinitionMap(perms);
}
}
這樣,就設定好了訪問每個頁面時所需要的許可權,但是隻有這些還不夠,因為,只是知道了訪問這些介面所需要的許可權,但是沒有給相應使用者新增許可權,否則,這些介面使用者是無法訪問的
步驟五:將使用者的所有許可權遍歷出來放到shiro中,讓shiro自行比對 在doGetAuthorizationInfo方法中
package com.qf.fayuan.shiro.realm;
import com.qf.fayuan.mapper.PermissonMapper;
import com.qf.fayuan.mapper.UserMapper;
import com.qf.fayuan.shiro.pojo.CourtAdmin;
import com.qf.fayuan.shiro.usernamepasswordtoken.UserNamePassWordToken;
import com.qf.fayuan.user.pojo.User;
import com.qf.fayuan.vo.UserVo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import java.util.List;
public class MyRealm extends AuthorizingRealm {
private UserMapper userMapper;
private PermissonMapper permissonMapper;
public void setPermissonMapper(PermissonMapper permissonMapper) {
this.permissonMapper = permissonMapper;
}
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
UserVo user = (UserVo) SecurityUtils.getSubject().getSession().getAttribute("user");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//獲取使用者的所有許可權
List<String> list = userMapper.getSelfPermisson(user.getUser_id());
if(list!=null){
for (String s : list) {
//將所有的許可權新增到shiro中
simpleAuthorizationInfo.addStringPermission(s);
}
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = ((UserNamePassWordToken) authenticationToken).getUsername();
int role = ((UserNamePassWordToken) authenticationToken).getUser_role();
CourtAdmin courtAdmin = userMapper.getCourtAdmin(username,role);
ByteSource bytes = ByteSource.Util.bytes(courtAdmin.getSalt());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,courtAdmin.getPassword(),bytes,getName());
return simpleAuthenticationInfo;
}
}
至此許可權部分完成