描述:
在工作中,其实用到权限认证的环境还挺多的,就想着做个小例子记录一把,留着以后复习。那就直奔主题了。
环境构建
1.创建SpringBoot项目
常规操作

目录结构如下
此为controller层,以及各种配置。Swagger大家不要管,只是为前端提供api查看以及测试。



<!--shiro管理控制-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
web层需要用到biz层,biz层需要用实体层那么就引入,以web层为例:
这样web层的pom文件引入后就可以访问service层的接口了
<dependencies>
<dependency>
<groupId>com.zk</groupId>
<artifactId>zk-biz</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
2.构建实体类以及用户角色权限枚举类
底下也都是Model层内容:
构建出自己的pojo,一个POJOBase为所以对象基类,用到uid所以这里只截uid


/**
* @Classname UserRole
* @Description 用户权限
* @Date 2019/12/30 10:14
* @Created by ZhangKai
*/
public enum UserRole {
//默认用户权限
// 普通用户
LEVE_0("0","普通用户"),
//牛逼用户
LEVE_1("1","牛逼用户"),
//未知权限
LEVE_999("999","未知权限");
private String code;
private String name;
UserRole(String code,String name){
this.code = code;
this.name =name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public static UserRole getEnumType(String key) {
UserRole[] alarmGrades = UserRole.values();
for (int i = 0; i < alarmGrades.length; i++) {
if (alarmGrades[i].getCode().equals(key)) {
return alarmGrades[i];
}
}
return UserRole.LEVE_999;
}
/**
* 根据Key得到枚举的Value
* Lambda表达式,比较判断(JDK 1.8)
*
* @param key
* @return
*/
public static UserRole getUserRole(String key) {
UserRole[] alarmGrades = UserRole.values();
UserRole result = Arrays.asList(alarmGrades).stream()
.filter(alarmGrade -> alarmGrade.getCode().equals(key))
.findFirst().orElse(UserRole.LEVE_999);
return result;
}
}
/**
* @Classname UserState
* @Description 用户状态
* @Date 2019/12/30 11:00
* @Created by ZhangKai
*/
public enum UserState {
//默认用户权限
// 普通用户
STATE_0("0","未登录"),
//牛逼用户
STATE_1("1","登录中"),
//未知权限
STATE_999("999","未知情况");
private String code;
private String name;
UserState(String code,String name){
this.code = code;
this.name =name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public static UserState getEnumType(String key) {
UserState[] alarmGrades = UserState.values();
for (int i = 0; i < alarmGrades.length; i++) {
if (alarmGrades[i].getCode().equals(key)) {
return alarmGrades[i];
}
}
return UserState.STATE_999;
}
/**
* 根据Key得到枚举的Value
* Lambda表达式,比较判断(JDK 1.8)
*
* @param key
* @return
*/
public static UserState getUserStateRole(String key) {
UserState[] alarmGrades = UserState.values();
UserState result = Arrays.asList(alarmGrades).stream()
.filter(alarmGrade -> alarmGrade.getCode().equals(key))
.findFirst().orElse(UserState.STATE_999);
return result;
}
}
3.重要的一步,extends AuthorizingRealm实现它的两个方法。
(1)简单写个service实现模拟返回数据库值
/**
* @Classname UserServiceImpl
* @Description TODO
* @Date 2019/12/27 16:05
* @Created by ZhangKai
*/
@Service
public class UserServiceImpl implements UserService {
Logger logger=LoggerFactory.getLogger(UserServiceImpl.class.getName());
//根据id判断是否存在此用户,用于查询权限状态
@Override
public User selectRoleByUser(String userId) {
User user=new User();
if("007".equals(userId)) {
user.setuId("007");
user.setuName("zhangsan");
user.setuPassword("123");
user.setuStatus(UserState.STATE_0.getCode());
user.setuLeveL(UserRole.LEVE_0.getCode());
user.setuSex(true);
user.setuWork("公职");
}
return user;
}
//根据账号为此用户做登录校验,密码校验在下方
@Override
public User selectRoleByName(String username) {
User user = new User();
if("zhangsan".equals(username)) {
user.setuId("007");
user.setuName("zhangsan");
user.setuPassword("123");
user.setuStatus(UserState.STATE_0.getCode());
user.setuLeveL(UserRole.LEVE_0.getCode());
user.setuSex(true);
user.setuWork("公职");
}
return user;
}
}
(2)权限认证以及登录校验(重要):
主要是biz层的内容:
此类需要多多注意的是此注入不是平常的@Autowired注解
@Resource private UserService userService;
/**
* @Classname ShiroRealm
* @Description TODO
* @Date 2019/12/27 17:01
* @Created by ZhangKai
*/
public class ShiroRealm extends AuthorizingRealm {
private static Logger logger = LoggerFactory.getLogger(ShiroRealm.class);
//注意此处 用的不是@Autowired注解,因为此层并非业务逻辑层,自动注入会报错
@Resource
private UserService userService;
/**
* 授权逻辑,根据用户信息,存入此用户所拥有的权限。判断权限校验不在此
* @return
* @throws AuthenticationException
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) throws AuthenticationException {
logger.info("-------- 权限配置 -------");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
POJOBase userId = (POJOBase) principals.getPrimaryPrincipal();
try {
//注入角色(查询所有的角色注入控制器)
logger.info("-------- 所传用户:"+userId.getuId()+"去查询该用户所拥有的权限");
//如果此用户存在
if (!EmptyObject.isEmpty(userId.getuId()))
{
User user=userService.selectRoleByUser(userId.getuId());
//获取此用户权限(正常权限需要判断是否有包含关系,如果没有就是集合)
// 此用户角色
authorizationInfo.addRole(user.getuLeveL());
//注入角色所有权限(查询用户所有的权限注入控制器)
//权限名称
authorizationInfo.addRole(UserRole.getUserRole(user.getuLeveL()).getName());
logger.info("查到该用户权限为"+user.getuLeveL()+":"+UserRole.getUserRole(user.getuLeveL()).getName());
}else {
logger.info("-------- 用户不存在 -------");
throw new UnknownAccountException("不存在此用户"); // 没有找到账号
}
}catch (Exception e)
{
logger.info(e.getMessage());
}
return authorizationInfo;
}
/**
* 用户登录认证逻辑,登录状态验证 1
* 当调用Subject currentUser = SecurityUtils.getSubject();
* currentUser.login(token);
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的输入的账号密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法,目前不做额外缓存
User user = userService.selectRoleByName(username);
if(null == user){
logger.info("-------- 用户不存在 -------");
throw new UnknownAccountException("不存在此用户");
}else {
if(password.equals(user.getuPassword())){
logger.info("=== 密码正确 ===");
if(UserState.STATE_1.getCode().equals(user.getuStatus())){
logger.info("=== 已登录 ===");
throw new LockedAccountException("已经登录请不要从新登录");
}else if (UserState.STATE_0.getCode().equals(user.getuStatus())){
logger.info("=== 登录通过 ===");
SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(
user,user.getuPassword().toCharArray(),getName());
return authorizationInfo;
}else {
logger.info("=== 未知状态 ===");
throw new DisabledAccountException();
}
} else {
logger.info("=== 密码错误 ===");
throw new IncorrectCredentialsException();
}
}
}
}
4.创建controller并增加shiro配置(重点)
(1)用户处理主要的类,此处 权限校验使用的是 @RequiresRoles()注解,你也可以使用subject.hasRole() 或 subject.isPermitted()的方式在Controller的方法里去判断。
/**
* @Classname UserController
* @Description TODO
* @Date 2019/12/27 16:07
* @Created by ZhangKai
*/
@Controller
@RequestMapping("/user")
@Api(description = "用户管理")
public class UserController {
private static Logger logger = LoggerFactory.getLogger(UserController.class);
@RequestMapping("/login")
@ApiOperation(value = "跳转用户页面" ,notes = "跳转用户页面")
public String login(HttpServletRequest request)
{
logger.info("=== 进入到登录页面 ===");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(request.getParameter("uName"),request.getParameter("uPassword"));
try {
logger.info("=== 开始登录校验 ===");
subject.login(token); //调用上面重载的方法doGetAuthenticationInfo
logger.info("=== 登录校验通过 ===");
} catch (UnknownAccountException e){
logger.info("=== 用户名不存在 ===");
// baseResponse.setMsg("用户名不存在");
} catch (IncorrectCredentialsException e){
e.printStackTrace();
logger.info("=== 密码错误 ===");
return "/errorPage.html";
} catch (LockedAccountException e){
logger.info("=== 锁定的帐号 ===");
return "/userLogin.html";
}catch (DisabledAccountException e){
logger.info("=== 禁用的帐号 ===");
} catch (Exception e){
e.printStackTrace();
}
return "/";
}
@RequestMapping("/userRole1")
@ApiOperation(value = "查看用户权限" ,notes = "查看用户权限")
//拥有此角色的才可以访问
@RequiresRoles("1")
@ResponseBody
public String userRole1()
{
logger.info("=== 拥有权限:牛逼用户 ===");
return "拥有牛逼用户权限";
}
@RequestMapping("/userRole0")
@ApiOperation(value = "查看用户权限" ,notes = "查看用户权限")
@ResponseBody
//拥有此角色的才可以访问
@RequiresRoles("0")
public String userRole0()
{
logger.info("=== 拥有权限:普通用户 ===");
return "拥有普通用户权限";
}
@GetMapping("/logout")
public String logout() {
//使用权限管理工具进行用户的退出,跳出登录,给出提示信息
SecurityUtils.getSubject().logout();
logger.info("您已安全退出");
return "redirect:/login";
}
}
(2)创建shiro配置管理(重点)
biz层内创建shiro配置
/**
* @Classname ShiroConfig
* @Description TODO
* @Date 2019/12/27 16:54
* @Created by ZhangKai
*/
@Configuration
public class ShiroConfig {
Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager security) {
logger.info("-------------------- shiro filter -------------------");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(security);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//注意过滤器配置顺序 不能颠倒
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/*", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/configuration/**", "anon");
//拦截其他所以接口
filterChainDefinitionMap.put("/**", "authc");
//配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
logger.info("=== 设置默认页面为:/userPage/userLogin");
shiroFilterFactoryBean.setLoginUrl("/userPage/userLogin");
// 登录成功后要跳转的链接 自行处理。不用shiro进行跳转
logger.info("=== 如果登录成功跳转:/index");
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
logger.info("=== 如果未授权跳转:/user/unImpower");
/*定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,需要继承FormAuthenticationFilter **本例中暂不自定义实现,在下一节实现验证码的例子中体现 */
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unImpower");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* shiro 用户数据注入
*
* @return
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
/**
* 配置管理层。即安全控制层
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
logger.info("注入Shiro的Web过滤器-->securityManager", ShiroFilterFactoryBean.class);
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* 权限管理,不加入此方法,没有权限验证
* shiro权限注解要生效,必须配置springAOP通过设置shiro的SecurityManager进行权限验证。
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 * @return
*
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 处理未授权的异常,返回自定义的错误页面(403)
* @return
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
/*未授权处理页*/
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/userPage/unImpower");
properties.setProperty("org.apache.shiro.authz.AuthorizationException", "/userPage/unImpower");
resolver.setExceptionMappings(properties);
return resolver;
}
/**
* 开启shiro aop注解支持 使用代理方式所以需要开启代码支持
* 一定要写入上面advisorAutoProxyCreator()自动代理。不然AOP注解不会生效
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器 * @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
当然,缓存配置主要也是在此处,目前先这样。然后启动调用看看吧。
调用http://localhost:8080/后发现进入到此页面,这也是上面配置的默认加载页面

<a href="/user/login?uName=zhangsan&uPassword=123">访问</a> 和上面serviceimpl的模拟返回值一样
点击访问后,运行 subject.login(token);然后调用 biz中ShiroRealm的doGetAuthenticationInfo方法

然后进入主页

点击牛逼权限

额对的, user.setuLeveL(UserRole.LEVE_0.getCode());给的是普通权限
然后试试普通的,点击普通权限。
