Springboot shiro整合
注意,本文是本地测试,缺少mybatis查询数据库
首先引入的项目包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!--<scope>test</scope>-->
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
编写主启动类
XXX指的是mapper文件位置
@SpringBootApplication
@MapperScan("XXX")
public class ShiroTestMain {
public static void main(String[] args) {
SpringApplication.run(ShiroTestMain.class, args);
}
}
实体类
public class User {
private Integer id;
private String username;
private String password;
//用户对应的角色集合
private List<Role> roleList;
getter...setter方法
}
public class Role {
private Integer id;
private String roleName;
getter...setter方法
}
public class Permissions {
private Integer id;
private String permissionName;
private List<Role> roleList;
getter...setter方法
}
shiroConfig类中的对象配置
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//添加shiro的内置过滤器
//anon:无需认证就可访问
//authc:必须认证了才能访问
//user:必须拥有 记住我 功能才能用
//perms:拥有对某个资源的取消权限才能访问
//role:拥有某个角色才能访问
//
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//anon 无需认证
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/front/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/user/**", "authc");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
return defaultSecurityManager;
}
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
// 告诉realm,使用credentialsMatcher加密算法类来验证密文
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
customRealm.setCachingEnabled(false);
return customRealm;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* *
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* * @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
// storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
realm类,进行授权,认证
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//角色,权限都从数据库中取
Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
if (primaryPrincipal instanceof User) {
User userLogin = (User) primaryPrincipal;
//查询user相关的角色,该方法可以改成一对多
Set<String> roles = new HashSet<>();
Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId());
info.addRoles(roles);
//查询user相关的权限
Set<String> permissions = new HashSet<>();
permissions.add("show");
permissions.add("admin");
//用户权限可以存储到数据库
Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId());
info.addStringPermissions(permissions);
}
return info;
}
/*
* 获取即将需要认证的信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("-------身份认证方法--------");
String userName = (String) authenticationToken.getPrincipal();
String userPwd = new String((char[]) authenticationToken.getCredentials());
//根据用户名从数据库获取密码,这里写死方便测试
String password = "557dcc0583b3288b97a070ed3f4bdea9";
if (userName == null) {
throw new AccountException("用户名不正确");
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
return new SimpleAuthenticationInfo(userName, password ,
ByteSource.Util.bytes(userName + "salt"), getName());
}
登录的LoginController
@RequestMapping(value = "/login", method = RequestMethod.GET)
@ResponseBody
public String defaultLogin() {
return "首页";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
//参数验证 判空
if(null==username.trim()|| null==password.trim()){
return "参数为空!";
}
// 检查用户状态
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
if (subject.isAuthenticated()) {
return "登录成功";
} else {
token.clear();
return "登录失败";
}
}
UserController类测试,权限
@RequiresPermissions({"show"})
@ResponseBody
@RequestMapping("/show")
public String showUser() {
return "展示信息";
}
全局的异常处理,返回无权限的提示信息。若没有该类,当没有权限时,会报错。
@ControllerAdvice
public class NoPermissionException {
//注意下注解@ControllerAdvice 和 @ExceptionHandler。
// @ControllerAdvice可以全局帮我们处理异常。
// @ExceptionHandler当有异常产生的时候,会自己跑到这个方法中
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String handleShiroException(Exception ex) {
return "无权限";
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String AuthorizationException(Exception ex) {
return "权限认证失败";
}
}