基于springboot的授权操作
首先而言,要进行授权,就是要实现看人下菜碟这个操作,即赋予成员相应的角色,针对不同的角色,可以访问相应的页面,使用相应的资源,首先,在数据表设计时,每个成员具有一个role属性,指定这个成员所属的角色,角色在对应一个授权表,完成角色和权限的一对多关系,下面是数据表的结构
customer:
下面是role,比较简单:
接下来是权限表:
在这个表中使用一个role_id完成和角色的对应,使得角色和权限完成一对多的映射
声明:以上数据表结构设计存在一些冗余和缺陷,然学习期间,一切从简
接下来我们需要进行授权,其实授权,说白了就是,资源的拦截和放行,在第一季已经做过,shiro总共有四种认证过滤器,第一季我们用了无需认证的anon和需要登陆才能进行操作的authc,下面可以使用perms进行授权,首先要进行资源的权限字符串的对应,这会精确到某个controller的某个方法,例如下面这个controller:
package com.example.lclepro.Controller;
import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Bean.Msg;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
UserService userService;
@PostMapping("/login")
public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password){
ModelAndView mv = new ModelAndView();
//获取到Subject对象
Subject suject = SecurityUtils.getSubject();
//将收集到的用户名和密码存储到token中
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//执行登入方法
try {
suject.login(token);
mv.setViewName("pages/customer");
}
catch (UnknownAccountException e){
//用户名不存在时抛出的异常
mv.addObject("msg", "用户不存在,请找到合适的介绍人加入组织(๑•̀ㅂ•́)و✧");
mv.setViewName("pages/index");
}
catch (IncorrectCredentialsException e){
//用户名密码不匹配时抛出的异常
mv.addObject("msg", "用户名与密码不匹配( ̄_, ̄ )");
mv.setViewName("pages/index");
}
return mv;
}
//使用ajax进行数据交互的方法,需要加上@ResponseBody注解
@GetMapping("/getAllCustomers")
@ResponseBody
public Msg getAllCustomers(){
return userService.getAllCustomers();
}
}
如果要对方法/getAllCustomers进行授权,就可以在自定义的Realm中完成授权方法如下
package com.example.lclepro.Config;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//实现拦截器
//@Bean注解注释在方法上,将返回的实体类交给容器处理,id是方法名
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置拦截成功以后,访问的页面
shiroFilterFactoryBean.setLoginUrl("/");
//拦截器Map的创建
Map<String, String> map = new HashMap<String, String>();
//针对不同的url,使用不同的拦截器,放到map中,即可生效,完成拦截
//拦截所有请求
map.put("/*", "authc");
//放行登陆请求
map.put("/", "anon");
//放行登录认证请求
map.put("/user/login", "anon");
map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
//设置相关的map集合,完成拦截功能的收集
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//实现web安全管理器的加载
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("lcleRealm") LcleRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//实现Realm的加载
@Bean
public LcleRealm lcleRealm(){
return new LcleRealm();
}
//要加入注解,指定方法的拦截,则需要加入以下的三个方法,开启注解模式
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* *
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* * @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
注意这一条代码:
map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
我们将,刚才那个方法,映射出了一个授权字符串即中括号里面的内容“user:getAllCustomers”,这时,用户进行访问,由于未进行授权,请求则会被拦截:
注意看第二个401,这就是我们请求的方法,他由于没有被授权,请求失败了,那么,如何进行授权操作呢,我们不妨在自定义的Realm中加上一句授权代码:
package com.example.lclepro.Config;
import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Service.UserService;
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.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class LcleRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//执行授权逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Subject subject = SecurityUtils.getSubject();
LcleCustomer customer = (LcleCustomer)subject.getPrincipal();
System.out.println(customer.getUsername()+"开始执行授权逻辑!");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:getAllCustomers");
return info;
}
//执行认证逻辑,执行login()方法以后,会进入到这里,进行用户名密码的比对
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//执行认证逻辑
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//获取密码
String password = userService.isUser(token.getUsername());
if(password==null){
throw new UnknownAccountException();
}
else{
LcleCustomer customer = userService.selectCustomerByName(token.getUsername());
return new SimpleAuthenticationInfo(customer, password, "");
}
}
}
注意这个授权方法中的代码`
Subject subject = SecurityUtils.getSubject();
LcleCustomer customer = (LcleCustomer)subject.getPrincipal();
System.out.println(customer.getUsername()+"开始执行授权逻辑!");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:getAllCustomers");
return info;
我们先从shiro中获取到,我们本次操作的对象,然后定义一个授权信息相关的String(多个授权也可以使用list),,如此我们就完成了简单的授权,重新启动页面,进行测试
我们看到,原先的401已经消失,资源访问正常,但是这样做未免会很傻,因为一个正常的项目,硬编码进去如此多的映射url,未免有些难受(尽管用户的权限可以持久化到数据库),这时候就体现出了注解的重要性,能否通过注解的方式,对每个controller的方法进行标记相应的授权url呢,答案是肯定的,不过我们的shiroConfig类需要做出变动,用来开启注解:
package com.example.lclepro.Config;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//实现拦截器
//@Bean注解注释在方法上,将返回的实体类交给容器处理,id是方法名
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置拦截成功以后,访问的页面
shiroFilterFactoryBean.setLoginUrl("/");
//拦截器Map的创建
Map<String, String> map = new HashMap<String, String>();
//针对不同的url,使用不同的拦截器,放到map中,即可生效,完成拦截
//拦截所有请求
map.put("/*", "authc");
//放行登陆请求
map.put("/", "anon");
//放行登录认证请求
map.put("/user/login", "anon");
map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
//设置相关的map集合,完成拦截功能的收集
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//实现web安全管理器的加载
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("lcleRealm") LcleRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//实现Realm的加载
@Bean
public LcleRealm lcleRealm(){
return new LcleRealm();
}
//要加入注解,指定方法的拦截,则需要加入以下的三个方法,开启注解模式
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* *
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* * @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
和之前相比,我们在最后加了三个方法,用来开启注解,那么我们接下来可以在每个方法头上加上一个注解@RequiresPermissions,用来标记这个方法的授权字符串,下面是controller的代码
package com.example.lclepro.Controller;
import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Bean.Msg;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping(“user”)
public class UserController {
@Autowired
UserService userService;
@PostMapping("/login")
public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password){
ModelAndView mv = new ModelAndView();
//获取到Subject对象
Subject suject = SecurityUtils.getSubject();
//将收集到的用户名和密码存储到token中
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//执行登入方法
try {
suject.login(token);
mv.setViewName("pages/customer");
}
catch (UnknownAccountException e){
//用户名不存在时抛出的异常
mv.addObject("msg", "用户不存在,请找到合适的介绍人加入组织(๑•̀ㅂ•́)و✧");
mv.setViewName("pages/index");
}
catch (IncorrectCredentialsException e){
//用户名密码不匹配时抛出的异常
mv.addObject("msg", "用户名与密码不匹配( ̄_, ̄ )");
mv.setViewName("pages/index");
}
return mv;
}
//使用ajax进行数据交互的方法,需要加上@ResponseBody注解
@RequiresPermissions("user:getAllCustomers")
@GetMapping("/getAllCustomers")
@ResponseBody
public Msg getAllCustomers(){
return userService.getAllCustomers();
}
}
至此,简单的授权已经完成,shiro的工作也基本完成,要想完成多个权限的操作,不妨将权限和角色进行映射,再将每个人赋予不同的角色,然后在自定义的Realm中使用Service方法,获取当前用户的授权字符串,逐个加入info中,完成授权
为之则易,不为则难