栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Shiro 注解篇03

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Shiro 注解篇03

功能:演示使用注解对url的权限控制以及无权限时跳转页面 1 github: https://github.com/yezhimincxvxb/shiro/tree/master/shiro03 2 shiro03子工程


    4.0.0

    
        com.yzm
        shiro
        0.0.1-SNAPSHOT
        ../pom.xml 
    

    shiro03
    0.0.1-SNAPSHOT
    jar
    shiro03
    Demo project for Spring Boot

    
        
            com.yzm
            common
            0.0.1-SNAPSHOT
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


项目结构

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test3?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: root
    password: root

mybatis-plus:
  mapper-locations: classpath:/mapper
public class MyShiroRealm extends AuthorizingRealm {

    private final UserService userService;
    private final RoleService roleService;
    private final PermissionsService permissionsService;

    public MyShiroRealm(UserService userService, RoleService roleService, PermissionsService permissionsService) {
        this.userService = userService;
        this.roleService = roleService;
        this.permissionsService = permissionsService;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) principalCollection.getPrimaryPrincipal();
        // 查询用户,获取角色ids
        User user = userService.lambdaQuery().eq(User::getUsername, username).one();
        List roleIds = Arrays.stream(user.getRIds().split(","))
                .map(Integer::parseInt)
                .collect(Collectors.toList());

        // 查询角色,获取角色名、权限ids
        List roles = roleService.listByIds(roleIds);
        Set roleNames = new HashSet<>(roles.size());
        Set permIds = new HashSet<>();
        roles.forEach(role -> {
            roleNames.add(role.getRName());
            Set collect = Arrays.stream(
                    role.getPIds().split(",")).map(Integer::parseInt).collect(Collectors.toSet());
            permIds.addAll(collect);
        });

        // 获取权限名称
        List permissions = permissionsService.listByIds(permIds);
        List permNames = permissions.stream().map(Permissions::getPName).collect(Collectors.toList());

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(roleNames);
        authorizationInfo.addStringPermissions(permNames);
        return authorizationInfo;
    }

    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取用户名跟密码
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        // 也可以这样获取
        //String username = (String) authenticationToken.getPrincipal();
        //String password = new String((char[]) authenticationToken.getCredentials());

        // 查询用户是否存在
        User user = userService.lambdaQuery().eq(User::getUsername, username).one();
        if (user == null) {
            throw new UnknownAccountException();
        }

        return new SimpleAuthenticationInfo(
                user.getUsername(),
                user.getPassword(),
                // 用户名 + 盐
                ByteSource.Util.bytes(user.getUsername() + user.getSalt()),
                getName()
        );
    }
}
ShiroConfig 配置类
package com.yzm.shiro03.config;

import com.yzm.shiro03.service.PermissionsService;
import com.yzm.shiro03.service.RoleService;
import com.yzm.shiro03.service.UserService;
import com.yzm.shiro03.utils.EncryptUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.cookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.cookie;
import org.apache.shiro.web.servlet.Simplecookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.Properties;

@Configuration
public class ShiroConfig {

    private final UserService userService;
    private final RoleService roleService;
    private final PermissionsService permissionsService;

    public ShiroConfig(UserService userService, RoleService roleService, PermissionsService permissionsService) {
        this.userService = userService;
        this.roleService = roleService;
        this.permissionsService = permissionsService;
    }

    
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(EncryptUtils.ALGORITHM_NAME);
        hashedCredentialsMatcher.setHashIterations(EncryptUtils.HASH_ITERATIONS);
        //true加密用的hex编码,false用的base64编码;默认true,本实例是toHex,可以查看EncryptUtils
        //hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    
    @Bean
    public MyShiroRealm shiroRealm() {
        MyShiroRealm shiroRealm = new MyShiroRealm(userService, roleService, permissionsService);
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

    
    @Bean
    public cookie simplecookie() {
        Simplecookie cookie = new Simplecookie("rememberMe");
        //设为true后,只能通过http访问,javascript无法访问
        //防止xss读取cookie
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        //存活时间,单位秒;-1表示关闭浏览器该cookie失效
        cookie.setMaxAge(-1);
        return cookie;
    }

    @Bean
    public cookieRememberMeManager rememberMeManager() {
        cookieRememberMeManager rememberMeManager = new cookieRememberMeManager();
        rememberMeManager.setcookie(simplecookie());
        //cookie加密的密钥
        //rememberMeManager.setCipherKey(base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        return rememberMeManager;
    }

    
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 配置单个realm
        securityManager.setRealm(shiroRealm());
        // 记住我
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 设置无权限时跳转的 url
        shiroFilterFactoryBean.setUnauthorizedUrl("/401");

//        Map definitionMap = new linkedHashMap<>();
//        definitionMap.put("/user
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        // 登录后没有权限跳转到/401
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/401");
        // 未登录访问接口跳转到/login
        properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "/login");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }

    
    @Bean
    public WebServerFactoryCustomizer webServerFactoryCustomizer() {
        return factory -> {
            ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401");
            ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404");
            ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500");
            factory.addErrorPages(error401Page, error404Page, error500Page);
        };
    }

}

4 开启注解
	
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
5 注解使用
@RequiresRoles 等同于 roles
@RequiresPermissions 等同于 perms
@RequiresAuthentication 等同于 authc
@RequiresUser 等同于 user
@RequiresGuest 跟 @RequiresUser 完全相反,即未登录也不能使用记住我功能,只能是游客

HomeController.java

package com.yzm.shiro03.controller;

import com.yzm.common.entity.HttpResult;
import com.yzm.shiro03.entity.User;
import com.yzm.shiro03.service.UserService;
import com.yzm.shiro03.utils.EncryptUtils;
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.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class HomeController {

    private final UserService userService;

    public HomeController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("login")
    public String login() {
        return "login";
    }

    @GetMapping("home")
    public Object home() {
        return "home";
    }

    @GetMapping("401")
    public Object notRole() {
        return "401";
    }
    
    @PostMapping("register")
    @ResponseBody
    public Object register(@RequestParam String username, @RequestParam String password) {
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        EncryptUtils.encryptPassword(user);
        userService.save(user);
        return HttpResult.ok();
    }

    @PostMapping("doLogin")
    @ResponseBody
    public Object doLogin(@RequestParam String username, @RequestParam String password, boolean rememberMe) {
        try {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
            if (rememberMe) usernamePasswordToken.setRememberMe(true);
            Subject subject = SecurityUtils.getSubject();
            subject.login(usernamePasswordToken);
        } catch (IncorrectCredentialsException ice) {
            return HttpResult.error("password error!");
        } catch (UnknownAccountException uae) {
            return HttpResult.error("username error!");
        }
        return HttpResult.ok();
    }

	// 自定义退出接口
    @GetMapping("/logout")
    public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated() || subject.isRemembered()) {
            subject.logout();
        }
        response.sendRedirect(request.getContextPath() + "/login");
    }

    @GetMapping("hello")
    @RequiresGuest //登录状态不能访问
    @ResponseBody
    public Object hello() {
        return "hello";
    }

    @GetMapping("list")
    @RequiresUser // 使用记住我功能时可以访问
    @ResponseBody
    public Object userList() {
        return userService.list();
    }

    @GetMapping("list2")
    @RequiresAuthentication
    @ResponseBody
    public Object userList2() {
        return userService.list();
    }

	@GetMapping("404")
    public Object notFound() {
        return "404";
    }

    @GetMapping("500")
    public Object error() {
        return "500";
    }

	@GetMapping("/fail")
    @ResponseBody
    public Object fail() {
        int i = 1/0;
        return "出错了";
    }
}

AdminController.java

package com.yzm.shiro03.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin")
@RequiresRoles("ADMIN")
public class AdminController {

    @GetMapping
    public Object admin() {
        return SecurityUtils.getSubject().getPrincipal();
    }

    @GetMapping("select")
    @RequiresPermissions("admin:select")
    public Object select() {
        return "Select";
    }

    @GetMapping("create")
    @RequiresPermissions("admin:create")
    public Object create() {
        return "Create";
    }

    @GetMapping("update")
    @RequiresPermissions("admin:update")
    public Object update() {
        return "Update";
    }

    @GetMapping("delete")
    @RequiresPermissions("admin:delete")
    public Object delete() {
        return "Delete";
    }

}

UserController.java

package com.yzm.shiro03.controller;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("//user")
@RequiresRoles(value = {"USER", "ADMIN"}, logical = Logical.OR)
    public class UserController {

    @GetMapping
    public Object user() {
        return SecurityUtils.getSubject().getPrincipal();
    }

    @GetMapping("select")
    @RequiresPermissions("user:select")
    public Object select() {
        return "Select";
    }

    @GetMapping("create")
    @RequiresPermissions("user:create")
    public Object create() {
        return "Create";
    }

    @GetMapping("update")
    @RequiresPermissions("user:update")
    public Object update() {
        return "Update";
    }

    @GetMapping("delete")
    @RequiresPermissions("user:delete")
    public Object delete() {
        return "Delete";
    }

    @GetMapping("createAndUpdate")
    //需要同时拥有
    @RequiresPermissions(value = {"user:create", "user:update"}, logical = Logical.AND)
    public Object createAndUpdate() {
        return "Create And Update";
    }

    @GetMapping("createOrUpdate")
    //拥有其中任意一个即可
    @RequiresPermissions(value = {"user:create", "user:update"}, logical = Logical.OR)
    public Object createOrUpdate() {
        return "Create Or Update";
    }

}

6 html页面

home.html




    
    首页


注册

admin

Admin角色

Admin角色,拥有 select 权限

Admin角色,拥有 create 权限

Admin角色,拥有 update 权限

Admin角色,拥有 delete 权限

user

User角色

User角色,拥有 select 权限

User角色,拥有 create 权限

User角色,拥有 update 权限

User角色,拥有 delete 权限

User角色,拥有 create and update 权限

User角色,拥有 create or update 权限

记住我

用户列表

用户列表2

注解

RequiresGuest 测试

报错

401.html跟login.html页面不需要改动

7 测试

启动项目,访问/home,当前未登录,点击admin或user下面的接口


全部都会报错的,控制台打印错误信息,而不是像之前一样跳转到登录页
shiroFilterFactoryBean.setLoginUrl("/login");
先不理它,访问/login 登录yzm用户

登录成功,继续访问
yzm的接口部分能访问,而admin的接口还是报错
这是由于没有权限原因,当我们使用注解方式时,访问没有权限的接口不会被shiroFilterFactoryBean.setUnauthorizedUrl("/401");处理

解决方法一,设置全局捕获

MyExceptionHandler.java

package com.yzm.shiro03.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Slf4j
@RestControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler
    public void ErrorHandler(AuthorizationException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
    	log.error("注解方式,自定义异常捕获");
        if (e instanceof UnauthenticatedException) {
            WebUtils.issueRedirect(request, response, "/login");
        } else if (e instanceof UnauthorizedException) {
            WebUtils.issueRedirect(request, response, "/401");
        }
    }

}


重新启动项目,未登录跳转到登录页,没权限跳转到401

解决方法二

在shiro配置类,注入

    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        // 登录后没有权限跳转到/401
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/401");
        // 未登录访问接口跳转到/login
        properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "/login");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }

注释掉解决方法一的全局捕获
不然还是会被全局的捕获,导致上面注入的方法无效

@Slf4j
//@RestControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler
    public void ErrorHandler(AuthorizationException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
    	log.error("注解方式,自定义异常捕获");
        if (e instanceof UnauthenticatedException) {
            WebUtils.issueRedirect(request, response, "/login");
        } else if (e instanceof UnauthorizedException) {
            WebUtils.issueRedirect(request, response, "/401");
        }
    }

}

重新启动项目,未登录时访问跳转到登录页,登录后访问没有权限跳转到/401,

访问/hello 测试@RequiresGuest,未登录可以访问;认证过或使用记住我功能拒绝访问
访问/list2 测试@RequiresAuthentication 认证过可以访问,其他时候拒绝访问
访问/list 测试@RequiresUser 认证过或使用记住我功能可以访问

@RequiresPermissions(value = {“user:create”, “user:update”}, logical = Logical.AND)
同时具备2个权限才能访问
@RequiresPermissions(value = {“user:create”, “user:update”}, logical = Logical.OR)
只需要拥有其中任意一个权限就可以访问
@RequiresRoles 跟 @RequiresPermissions 使用差不多的

7 自定义错误页面

在shiro配置了中添加

    @Bean
    public WebServerFactoryCustomizer webServerFactoryCustomizer() {
        return factory -> {
        	// 可以这样写,但效果没有上面的两种方案好
        	//ErrorPage errorLoginPage = new ErrorPage(UnauthenticatedException.class, "/login");
            //ErrorPage error401Page = new ErrorPage(UnauthorizedException.class, "/401");
            ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401");
            ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404");
            ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500");
            factory.addErrorPages(error401Page, error404Page, error500Page);
        };
    }

在HomeController.java 新增接口

	@GetMapping("/fail")
    @ResponseBody
    public Object fail() {
        int i = 1/0;
        return "出错了";
    }

    @GetMapping("404")
    public Object notFound() {
        return "404";
    }

    @GetMapping("500")
    public Object error() {
        return "500";
    }

新增404、500页面,内容跟401差不多




    
    500


服务器崩溃了

重启,访问/fail 跳转到500页面,访问不存在的url跳转到404页面

8 注解跟拦截器同时使用,拦截器优先

例如:在ShiroConfig#shiroFilter

    @Bean
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 设置无权限时跳转的 url
        shiroFilterFactoryBean.setUnauthorizedUrl("/401");

        Map definitionMap = new linkedHashMap<>();
        definitionMap.put("/user/**", "roles[ADMIN]");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(definitionMap);
        return shiroFilterFactoryBean;
    }

debug role注解处理器和role拦截器


重启,登录yzm,访问/user,先进的是拦截器,但拦截器配置的需要admin角色,yzm只有user角色
所以直接跳转到401页面,不再继续注解处理器

如果拦截器通过,就还会继续注解处理器,只有都通过才能访问

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/327841.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号