工程第一步:导入依赖:
pom添加:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot集成Junit 的启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--thymeleaf启动依赖,它是一个前端模板引擎,完全替代jsp-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<!--引入的是shiro与spring的集成-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!--aop 的起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
// 读取配置文件 获得权限管理类的工厂
IniSecurityManagerFactory securityManagerFactory=new IniSecurityManagerFactory("classpath:shiro2.ini");
// 获取securityManager
SecurityManager securityManager= securityManagerFactory.getInstance();
// 工具安全类, 让他持有securityManager ,因为要发生关系
SecurityUtils.setSecurityManager(securityManager);
//获取当前用户
Subject subject= SecurityUtils.getSubject();
AuthenticationToken authenticationToken=new UsernamePasswordToken("root","123456");
try {
subject.login(authenticationToken);
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
根据这个图,首先我们用shiro肯定会有认证这一步,这一步会有登录,在登录的底层会有认证和授权,这个都是通过链接数据库读取数据得到的,这两个方法的话,如果你只需要重写认证的话,呢么需要创建一个类,这个类需要继承
AuthenticatingRealm(认证域)类,这个类中需要重写一个方法,是
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
在这个类中,我们需要返回一个对象,这个对象是用户的证明信息,是真实的信息,即从数据库内拿到的真实数据,shiro需要通过这个数据跟前台传入的账户和密码进行比对来看看是否登录成功。但是这个类有一个弊端就是他只能完成认证的操作。不能授权。
public class CustomerRealm extends AuthenticatingRealm {
@Override // 这个类的作用就是获取登录的账号,然后去查询数据库的密码之后进行返回
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username= (String) authenticationToken.getPrincipal();
// 根据用户名去数据库查询密码
String password="123456";
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(username,password,this.getName());
return authenticationInfo;
}
}
所以我们就有了第二种继承类:继承
AuthorizingRealm(授权域)类。 这个类中重写了两个方法,分别是认证和授权;
doGetAuthenticationInfo;认证
doGetAuthorizationInfo;授权
其中认证和上述继承认证的思路一样,授权方法的话,主要思路就是通过登录的账户名去数据库内寻找对应的这个账户所拥有的角色和权限。然后循环赋值给这个用户,以用来之后页面鉴权的操作。
public class UserRealm extends AuthorizingRealm {
@Override// 认证方法 主要的作用就是返回真正的用户名和密码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username= (String) authenticationToken.getPrincipal();
String password="123456";
AuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(username,password,this.getName());
return authenticationInfo;
}
@Override // 授权方法 主要的作用就是根据登录的账户名去数据库内查找对应的所有角色和权限,并且赋予给他,这里是做了一个模拟
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username= (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
// 去数据库查询root相关的权限和角色 root是登录名
if (username.endsWith("root")){
authorizationInfo.addRole("manager");
authorizationInfo.addStringPermission("goods:add");
authorizationInfo.addStringPermission("goods:delete");
}
return authorizationInfo;
}
}
这样我们就完成了登录的认证和授权操作。接下来我们就要进行设置shiro的过滤器,因为只有使用了过滤器拦截,用shiro和spingboot整合的工程才有意义。
整个shiro的配置可以用以下步骤总结:
1、读取重写的认证授权方法 *2、获取securityManager * 3、将securityManager注入securityUtil 在这里过滤器会自动完成这一操作。配置过滤器完成权限的鉴别操作。 * 4、通过securityUtil获取subject,即可完成登录等操作。
而shiro过滤器的设置是基于xml文件来进行的,我们可以创建一个类,在类名顶加上注释:
@Configuration // 表明当前类是一个配置类 相当于beans.xml文件
@Bean 的作用是将当前的方法加入容器,相当于bean标签
先给出完整的代码:
@Configuration // 表明当前类是一个配置类 相当于beans.xml文件
public class ShiroConfig {
@Bean // 将创建的UserRealm加入容器
public UserRealm getUserRealm(){
return new UserRealm();
}
// 向容器中加入SecurityManager getSecurityManager
// 当创建SecurityManager的时候,回去容器中去查找一个UserRealm类型的Bean
@Bean
SecurityManager getSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
/*
* 在系统初始化AbstractShiroFilter的时候根据
staticSecurityManagerEnabled的值而将securityManager设置到SecurityUtils中。
* */
@Bean // 设置shiro过滤器 作用就是过滤拦截 鉴权当前用户是否有 对应的角色和权限
ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
// 将securityManager 和拦截器进行绑定
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 没有登录需要跳到登录界面
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
// 当已经认证成功 自动跳转的界面
shiroFilterFactoryBean.setSuccessUrl("/user/toIndex");
// 当登陆后,但是权限不足时 跳转的界面 注解的权限或者角色不足时,不能跳转,需要自己手动处理
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauthorized");
Map<String, String> filterChainDefinitionMap = new HashMap<>();
// 设置当前路径 不被shiro 拦截
filterChainDefinitionMap.put("/user/login","anon");
// 配置游客路径 不拦截 无论是否登陆 都可以访问
filterChainDefinitionMap.put("/guest/*","anon");
// 访问当前路径 roles[root]当前必须拥有root角色 , roles[root,manager] 当前用户必须拥有 列那个角色
filterChainDefinitionMap.put("/user/add","roles[root]");
// 访问当前路径 必须拥有 user:delete
filterChainDefinitionMap.put("/user/delete","perms[user:delete]");
// 拦截所有路径 ***** 一定要写在再最后
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean // 作用就是处理基于注解的权限 @Require呢几个
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
// 将 authorizationAttributeSourceAdvisor 和SecurityManager进行绑定
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean // 等价于aop注解中的 <aop:aspectj-autoproxy> 开启aop注解 就会向容器注入一个DefaultAdvisorAutoProxyCreator
@ConditionalOnMissingBean // 当容器中没有当前类的时候注入,如果有了的话就不注入
DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
// 优先使用cglib 完成代理
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean // 当前异常 处理的就是 shiro 基于注解的鉴权是,不满足权限的需要抓住异常
SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver=new SimpleMappingExceptionResolver();
// properties 就是读取配置文件的键值对
// 这里的key就是捕捉的异常 具体可以参考xml里边的配置properties
Properties properties=new Properties();
properties.setProperty("exception","/403.html");
resolver.setExceptionMappings(properties);
return resolver;
}
}
然后对照分步解读一下:
1、读取重写的认证授权方法:
@Bean // 将创建的UserRealm加入容器
public UserRealm getUserRealm(){
return new UserRealm();
}
2、获取securityManager
// 向容器中加入SecurityManager getSecurityManager
// 当创建SecurityManager的时候,回去容器中去查找一个UserRealm类型的Bean
@Bean
SecurityManager getSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
3、将securityManager注入securityUtil 在这里过滤器会自动完成这一操作。这里也包含了过滤器的设置,需要返回一个shirofilterFacrotybean的过滤器实体。需要注意的一点是:
我们的所有过滤权限操作都在这里进行配置(但是不是必须配置的,这里的情况仅限于不使用注解配置) ,其中
* 没有登录的时候需要跳转的界面
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
* 认证成功后跳转的界面
shiroFilterFactoryBean.setSuccessUrl("/user/toIndex");
* 登录之后权限不足的时候跳转的界面
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauthorized");
这几个界面是必须要配置的。
其余资源的权限配置,比如说:我登录后,哪些操作需要有什么样子的角色权限,什么操作需要有资源权限,以及各个界面是否拦截,都是酌情配置的。
其中的话,
在权限map的<key,value>中,value的值为roles[root] 的意思是当前路径必须要有root角色。
value为perms[user:delete] 的意思是当前路径必须要有user:delete资源才可以访问。
而拦截所有路径<"/**",“authc”> 一定要写在最后。
/*
* 在系统初始化AbstractShiroFilter的时候根据
staticSecurityManagerEnabled的值而将securityManager设置到SecurityUtils中。
* */
@Bean // 设置shiro过滤器 作用就是过滤拦截 鉴权当前用户是否有 对应的角色和权限
ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
// 将securityManager 和拦截器进行绑定
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 没有登录需要跳到登录界面
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
// 当已经认证成功 自动跳转的界面
shiroFilterFactoryBean.setSuccessUrl("/user/toIndex");
// 当登陆后,但是权限不足时 跳转的界面 注解的权限或者角色不足时,不能跳转,需要自己手动处理
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauthorized");
Map<String, String> filterChainDefinitionMap = new HashMap<>();
// 设置当前路径 不被shiro 拦截
filterChainDefinitionMap.put("/user/login","anon");
// 配置游客路径 不拦截 无论是否登陆 都可以访问
filterChainDefinitionMap.put("/guest/*","anon");
// 访问当前路径 roles[root]当前必须拥有root角色 , roles[root,manager] 当前用户必须拥有 列那个角色
filterChainDefinitionMap.put("/user/add","roles[root]");
// 访问当前路径 必须拥有 user:delete
filterChainDefinitionMap.put("/user/delete","perms[user:delete]");
// 拦截所有路径 ***** 一定要写在再最后
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
4、通过securityUtil获取subject,即可完成登录等操作。
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("toIndex")
public String toIndex(){
return "index";
}
@RequestMapping("login")
public String login(String username,String password){
// 获取当前数据
Subject subject= SecurityUtils.getSubject();
AuthenticationToken token=new UsernamePasswordToken(username,password);
subject.login(token);
if (subject.isAuthenticated()){
return "index";
}else {
return "login";
}
}
@RequestMapping("logout")
public String logout(){
// 获取当前用户
Subject subject=SecurityUtils.getSubject();
if (subject.isAuthenticated()){
subject.logout();
}
return "login";
}
@RequestMapping("/unauthorized")
public String unauthorized(){
return "403";
}
@RequestMapping("/add")
public String userAdd(){
return "addUser";
}
@RequestMapping("/delete")
public String userDelete(){
return "deleteUser";
}
分割线*********************************************************************************************************************************
以上就是所有的自定义认证授权和配置拦截器完成shiro鉴权的操作,上述操作是在类似xml文件中,一般来说有xml文件配置形式就会有注解配置形式。下边来介绍注解的配置形式:
***********************************************************************************************************************************************
通过注解配置拦截器
还是在上边的xml配置文件中,我们需要加入下边的几个配置:
需要加入一个AuthorizationAttributeSourceAdvisor(授权属性来源通知)的实体,用get的方式。
@Bean // 作用就是处理基于注解的权限 @Require呢几个
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
// 将 authorizationAttributeSourceAdvisor 和SecurityManager进行绑定
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
看见advisor,我们就想起来了aop,也即切面切点等。在这里我们要用注解形式,所以用激活aop的注解,下边的语句跟aop的xml文件配置中的 <aop:aspectj-autoproxy> 等价:
@Bean // 等价于aop注解中的 <aop:aspectj-autoproxy> 开启aop注解 就会向容器注入一个DefaultAdvisorAutoProxyCreator
@ConditionalOnMissingBean // 当容器中没有当前类的时候注入,如果有了的话就不注入
DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
// 优先使用cglib 完成代理
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
然后就可以在控制层开开心心的使用注解来配置拦截器了
@Controller
@RequestMapping("goods")
public class GoodsController {
@RequiresAuthentication// 只要当前用户登录就可以查看
@RequestMapping("getGoodsList")
public String getGoods(){
return "goodslist";
}
@RequestMapping("addGoods")
@RequiresPermissions("goods:add") // 必须要求访问当前路径,必须有goods:add 权限
public String addGoods(){
return "addgoods";
}
@RequestMapping("deleteGoods")
@RequiresRoles("boss") // 要求访问当前路径必须是boss
public String deleteGoods(){
return "deletegoods";
}
最后的最后,还需要注意的一点就是:在拦截器中配置的权限不足跳转界面,在注解中是不生效的,所以我们可以配置一个全局异常声明来完成注解的权限不足跳转界面:
@Bean // 当前异常 处理的就是 shiro 基于注解的鉴权是,不满足权限的需要抓住异常
SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver=new SimpleMappingExceptionResolver();
// properties 就是读取配置文件的键值对
// 这里的key就是捕捉的异常 具体可以参考xml里边的配置properties
Properties properties=new Properties();
properties.setProperty("exception","/403.html");
resolver.setExceptionMappings(properties);
return resolver;
}