相关推荐recommended
02.SpringBoot3+JDK17+Shiro+Basic认证方式
作者:mmseoamin日期:2024-02-04

SpringBoot3+JDK17+Shiro+Basic认证方式

依赖

注意: 由于JDK17使用的是Jakarta EE规范,而截止2023年12月29日Shiro2.0还处于(alpha)测试阶段,所以只能使用目前最新的版本shiro1.13,但是Shiro1.13版本目前默认使用的是Java EE规范,所以不能直接引入shiro-spring-boot-web-starter依赖


		
			org.projectlombok
			lombok
		
		
			org.springframework.boot
			spring-boot-starter-web
		
		
			org.apache.shiro
			shiro-spring
			jakarta
			1.13.0
			
				
					org.apache.shiro
					shiro-core
				
				
					org.apache.shiro
					shiro-web
				
			
		
		
			org.apache.shiro
			shiro-core
			jakarta
			1.13.0
		
		
			org.apache.shiro
			shiro-web
			jakarta
			1.13.0
			
				
					org.apache.shiro
					shiro-core
				
			
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
			mysql
			mysql-connector-java
			8.0.33
		
		
			com.baomidou
			mybatis-plus-boot-starter
			3.5.3.2
		
	

涉及表结构(核心字段)

  1. 用户表(sys_user)

    FieldTypeNullKeyDefaultExtraComment
    user_idbigintNOPRINULLauto_increment
    usernamevarchar(50)NOUNI登录用户名
    namevarchar(128)YESNULL姓名
    passwordvarchar(100)YESNULL密码
  2. 角色表(sys_role)

    FieldTypeNullKeyDefaultExtraComment
    role_idbigintNOPRINULLauto_increment
    role_namevarchar(100)YESNULL角色名称
    remarkvarchar(100)YESNULL备注
  3. 权限表/菜单表(sys_menu)

    FieldTypeNullKeyDefaultExtraComment
    perm_idbigint unsignedNOPRINULLauto_increment
    permsvarchar(500)YESNULL授权(多个用逗号分隔,如:user:list,user:create)
  4. 用户角色表(sys_user_role)

    FieldTypeNullKeyDefaultExtraComment
    idbigintNOPRINULLauto_increment
    user_idbigintYESNULL用户ID
    role_idbigintYESNULL角色ID
  5. 角色权限表(sys_role_menu)

    FieldTypeNullKeyDefaultExtraComment
    idbigintNOPRINULLauto_increment
    role_idbigintYESNULL角色ID
    perm_idbigintYESNULL权限ID

springboot配置

server:
  port: 端口号
spring:
  datasource:
    type: com.mysql.cj.jdbc.MysqlDataSource
    username: 数据库账号
    password: 数据库密码
    url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
mybatis-plus:
  global-config:
    banner: off
logging:
  level:
    org.apache.shiro.authc.AbstractAuthenticator: debug

shiro配置

  1. 开启shiro注解支持

    注意:如果不配置这两货,使用shiro的注解时会失效

    @Configuration
    public class ShiroConfig {
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
            DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
            defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
            return defaultAdvisorAutoProxyCreator;
        }
        
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }
    }
    
  2. 配置LifecycleBeanPostProcessor

    @Configuration
    public class ShiroConfig {
        /**
         * 用于管理shiro组件的生命周期
         * @return
         */
        @Bean("lifecycleBeanPostProcessor")
        public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    }
    
  3. 配置SecurityManager

    @Configuration
    public class ShiroConfig {
        @Bean("securityManager")
        public SecurityManager securityManager(JdbcRealm jdbcRealm) {
            return new DefaultWebSecurityManager(jdbcRealm);
        }
    }
    
  4. 配置Realm

    注意1:由于我使用的是内置的JDBCRealm实现认证逻辑的,所以配置了数据源,在springboot的yml文件中配置好数据源,直接注入就好

    注意2:认证逻辑可以查看JDBCRealm中的doGetAuthenticationInfo方法,JDBCRealm默认查询的表是users(用户表)、user_roles(用户角色表)、roles_permissions(角色权限表)这三张表。如果你的表名不同,修改对应的sql(以下配置中又修改示例);

    注意3:realm可以自定义,也可以选择其他Realm。

        @Bean("jdbcRealm")
        public JdbcRealm jdbcRealm() {
            JdbcRealm jdbcRealm = new JdbcRealm();
            //修改查询用户的sql
            String authenticationQuery = "select password from sys_user where username = ?";
            //修改查询用户角色的sql
            String userRolesQuery = "select t2.role_id from sys_user t1,sys_user_role t2 where t1.user_id = t2.user_id and t1.username = ?";
            //修改查询角色权限的sql
            String permissionsQuery = "select perms from sys_menu t1,sys_role_menu t2 where t1.menu_id = t2.menu_id and role_id = ?";
            //设置允许查询权限(默认是false)
            jdbcRealm.setPermissionsLookupEnabled(true);
            jdbcRealm.setAuthenticationQuery(authenticationQuery);
            jdbcRealm.setUserRolesQuery(userRolesQuery);
            jdbcRealm.setPermissionsQuery(permissionsQuery);
            //设置数据源
            jdbcRealm.setDataSource(dataSource);
            return jdbcRealm;
        }
    
  5. 配置过滤器链

    @Configuration
    public class ShiroConfig {
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            Map filterMap = new LinkedHashMap<>();
            filterMap.put("/shiro/first", "anon");//设置不需要认证的url
            filterMap.put("/**","authcBasic");//设置需要Basic方式认证的url
            shiroFilter.setFilterChainDefinitionMap(filterMap);
            shiroFilter.setSecurityManager(securityManager);
            shiroFilter.setGlobalFilters(Arrays.asList("noSessionCreation"));//设置无状态服务(禁用会话)
            return shiroFilter;
        }
    }
    
  6. 整体的配置如下:

    @Configuration
    public class ShiroConfig {
        @Autowired
        private DataSource dataSource;
        /**
         * 用于管理shiro组件的生命周期
         *
         * @return
         */
        @Bean("lifecycleBeanPostProcessor")
        public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
        @Bean("jdbcRealm")
        public JdbcRealm jdbcRealm() {
            JdbcRealm jdbcRealm = new JdbcRealm();
            //修改查询用户的sql
            String authenticationQuery = "select password from sys_user where username = ?";
            //修改查询用户角色的sql
            String userRolesQuery = "select t2.role_id from sys_user t1,sys_user_role t2 where t1.user_id = t2.user_id and t1.username = ?";
            //修改查询角色权限的sql
            String permissionsQuery = "select perms from sys_menu t1,sys_role_menu t2 where t1.menu_id = t2.menu_id and role_id = ?";
            //设置允许查询权限(默认是false)
            jdbcRealm.setPermissionsLookupEnabled(true);
            jdbcRealm.setAuthenticationQuery(authenticationQuery);
            jdbcRealm.setUserRolesQuery(userRolesQuery);
            jdbcRealm.setPermissionsQuery(permissionsQuery);
            //设置数据源
            jdbcRealm.setDataSource(dataSource);
            return jdbcRealm;
        }
        @Bean("securityManager")
        public SecurityManager securityManager(JdbcRealm jdbcRealm) {
            return new DefaultWebSecurityManager(jdbcRealm);
        }
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            Map filterMap = new LinkedHashMap<>();
            filterMap.put("/shiro/anon", "anon");//设置不需要认证的url
            filterMap.put("/**", "authcBasic");//设置需要Basic方式认证的url
            shiroFilter.setFilterChainDefinitionMap(filterMap);
            shiroFilter.setSecurityManager(securityManager);
            shiroFilter.setGlobalFilters(Arrays.asList("noSessionCreation"));//设置无状态服务(禁用会话)
            return shiroFilter;
        }
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
            DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
            defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
            return defaultAdvisorAutoProxyCreator;
        }
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }
    }
    

异常处理配置

@ControllerAdvice
@Slf4j
public class ExceptionHandlerConfig {
    @ExceptionHandler(AuthorizationException.class)
    @ResponseBody
    public String handleException(AuthorizationException e) {
        log.info("AuthorizationException was thrown", e);
        return "授权失败";
    }
    @ExceptionHandler(AuthenticationException.class)
    @ResponseBody
    public String handleException(AuthenticationException e) {
        log.info("AuthenticationException was thrown", e);
        return "登录失败";
    }
}

测试类(controller)

@RestController
@RequestMapping("/shiro")
public class ShiroController {
    @GetMapping("/anon")
    public String anon() {
        return "不需要认证授权的url";
    }
    @GetMapping("/authentication")
    public String authentication() {
        return "认证成功";
    }
    @RequiresPermissions("sys:perm:read")
    @GetMapping("/permRead")
    public String permRead() {
        return "授权:读";
    }
    @RequiresPermissions("sys:perm:write")
    @GetMapping("/permWrite")
    public String permWrite() {
        return "授权:写";
    }
    //这里是角色id
    @RequiresRoles("1")
    @GetMapping("/roleSys")
    public String roleSys() {
        return "授权:系统管理员";
    }
    //这里是角色id
    @RequiresRoles("2")
    @GetMapping("/roleCom")
    public String roleCom() {
        return "授权:普通管理员";
    }
    //用户的信息
    @GetMapping("/info")
    public String info() {
        Subject subject = SecurityUtils.getSubject();
        Object principal = subject.getPrincipal();
        boolean role1 = subject.hasRole("1");
        boolean role2 = subject.hasRole("2");
        boolean write = subject.isPermitted("sys:perm:write");
        boolean read = subject.isPermitted("sys:perm:read");
        return "principal :" + principal + " write:" + write + " read:" + read + " role1:" + role1 + " role2:" + role2;
    }
}

测试

**注意1:**可以使用postman测试,也可以直接浏览器测试。博主图方便直接浏览器测试了

**注意2:**测试的用户名test002,该用户是普通管理员角色(角色id为2),只有读的权限没有写的权限