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>
涉及表结构(核心字段)
-
用户表(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 密码 -
角色表(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 备注 -
权限表/菜单表(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) -
用户角色表(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 -
角色权限表(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配置
-
开启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; } }
-
配置LifecycleBeanPostProcessor
@Configuration public class ShiroConfig { /** * 用于管理shiro组件的生命周期 * @return */ @Bean("lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } }
-
配置SecurityManager
@Configuration public class ShiroConfig { @Bean("securityManager") public SecurityManager securityManager(JdbcRealm jdbcRealm) { return new DefaultWebSecurityManager(jdbcRealm); } }
-
配置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; }
-
配置过滤器链
@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; } }
-
整体的配置如下:
@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
- 验证需要认证的url:
shiro/authentication
- 验证需要特定角色才能访问的url:
shiro/roleSys
、shiro/roleCom
- 验证需要特定权限才能访问的url(前提已经认证,如果没有认证需要先认证,也就是登录):
shiro/permRead
、shiro/permWrite
- 获取用户的基本信息(前提已经认证,如果没有认证需要先认证,也就是登录):
shiro/info
源码
最后贴出源码,希望给个赞!谢谢