02.SpringBoot3+JDK17+Shiro+Basic认证方式

发布时间 2024-01-02 16:37:33作者: 小超and车儿

SpringBoot3+JDK17+Shiro+Basic认证方式

依赖

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

<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<classifier>jakarta</classifier>
			<version>1.13.0</version>
			<exclusions>
				<exclusion>
					<groupId>org.apache.shiro</groupId>
					<artifactId>shiro-core</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.apache.shiro</groupId>
					<artifactId>shiro-web</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<classifier>jakarta</classifier>
			<version>1.13.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<classifier>jakarta</classifier>
			<version>1.13.0</version>
			<exclusions>
				<exclusion>
					<groupId>org.apache.shiro</groupId>
					<artifactId>shiro-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.33</version>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.3.2</version>
		</dependency>

	</dependencies>

涉及表结构(核心字段)

  1. 用户表(sys_user)

    Field Type Null Key Default Extra Comment
    user_id bigint NO PRI NULL auto_increment
    username varchar(50) NO UNI 登录用户名
    name varchar(128) YES NULL 姓名
    password varchar(100) YES NULL 密码
  2. 角色表(sys_role)

    Field Type Null Key Default Extra Comment
    role_id bigint NO PRI NULL auto_increment
    role_name varchar(100) YES NULL 角色名称
    remark varchar(100) YES NULL 备注
  3. 权限表/菜单表(sys_menu)

    Field Type Null Key Default Extra Comment
    perm_id bigint unsigned NO PRI NULL auto_increment
    perms varchar(500) YES NULL 授权(多个用逗号分隔,如:user:list,user:create)
  4. 用户角色表(sys_user_role)

    Field Type Null Key Default Extra Comment
    id bigint NO PRI NULL auto_increment
    user_id bigint YES NULL 用户ID
    role_id bigint YES NULL 角色ID
  5. 角色权限表(sys_role_menu)

    Field Type Null Key Default Extra Comment
    id bigint NO PRI NULL auto_increment
    role_id bigint YES NULL 角色ID
    perm_id bigint YES NULL 权限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<String, String> 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<String, String> 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),只有读的权限没有写的权限

  • 验证不需要认证的url:shiro/anon
    shiro/anon
  • 验证需要认证的url:shiro/authentication
    shiro/authentication
  • 验证需要特定角色才能访问的url:shiro/roleSysshiro/roleCom
    shiro/roleSys
    shiro/roleCom
  • 验证需要特定权限才能访问的url(前提已经认证,如果没有认证需要先认证,也就是登录):shiro/permReadshiro/permWrite
    shiro/permRead
    shiro/permWrite
  • 获取用户的基本信息(前提已经认证,如果没有认证需要先认证,也就是登录):shiro/info
    shiro/info