文章简述
本文通过使用SpringBoot整合Shiro实现认证、授权功能,仅提供简单实现操作。
关键代码实现
Shiro依赖引入
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
配置文件参数配置
非必须,可自行决定其他配置方式
# shiro
shiro:
user:
loginUrl: /user/login
unauthUrl: /user/unauth
# minutes
expireTime: 30
自定义Realm
/**
* @ClassName: UserRealm
* @Description: 自定义Realm
* @Date: 2021/7/13 17:50
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private MenuService menuService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑");
// 获取当前登录用户
User user = (User) SecurityUtils.getSubject().getPrincipal();
// 角色列表
Set<String> roles = new HashSet<String>();
// 功能列表
Set<String> menus = new HashSet<String>();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if("admin".equals(user.getLoginName())){
// 管理员分配全部权限
info.addRole("admin");
info.addStringPermission("*:*:*");
}else {
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 角色加入AuthorizationInfo认证对象
info.setRoles(roles);
// 权限加入AuthorizationInfo认证对象
info.setStringPermissions(menus);
}
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
String username = upToken.getUsername();
String password = "";
if (upToken.getPassword() != null) {
password = new String(upToken.getPassword());
}
User user = userService.checkLogin(username, password);
if (null == user) {
throw new UnknownAccountException("登录账号或密码错误,请重新输入!");
}
return new SimpleAuthenticationInfo(user, password, getName());
}
}
定义Shiro配置类
/**
* @ClassName: ShiroConfig
* @Description: Shiro配置类
* @Date: 2021/7/6 17:14
*/
@Configuration
public class ShiroConfig {
/**
* 登录地址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
return new UserRealm();
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 登录地址(仅仅设置登录路径,但是登录路径并没有设置anon)
shiroFilterFactoryBean.setLoginUrl(loginUrl);
/**
* 添加Shiro内置过滤器
* anon:无需认证(登录)即可访问
* authc:必须认证才能访问
* user:如果使用rememberMe的功能可以直接访问
* perms:该资源必须得到资源权限才可以访问
* role:该资源必须得到角色权限才可以访问
*/
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/**
* swagger
*/
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/configuration/ui", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/configuration/security", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
/**
* 退出 logout地址,shiro去清除session
*/
// filterChainDefinitionMap.put("/logout", "logout");
/**
* 无需认证
*/
// 设置登录路径anon,否则访问登录接口,会多执行一次认证方法(token为空)
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/testShiro", "anon");
/**
* 其他均需要认证
*/
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* Shiro生命周期处理器
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
/**
* * 开启shiro aop注解支持.
* * 使用代理方式;所以需要开启代码支持;
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
定义异常类统一处理认证/授权失败跳转URL
/**
* @ClassName: ExceptionHandle
* @Description: 统一异常处理类
* @Date: 2021/7/14 10:54
*/
@RestControllerAdvice
public class ExceptionHandle {
/**
* 登录失败跳转地址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
/**
* 权限认证失败跳转地址
*/
@Value("${shiro.user.unauthUrl}")
private String unauthUrl;
// private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);
/**
* 授权失败默认跳转的请求地址
*
* @param ex
* @return
*/
// @ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public Object handleShiroException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(unauthUrl);
return modelAndView;
}
/**
* 认证失败默认跳转的请求地址
*
* @param ex
* @return
*/
// @ResponseBody
@ExceptionHandler(AuthorizationException.class)
public Object AuthorizationException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(loginUrl);
return modelAndView;
}
}
使用
/**
* @ClassName: UserController
* @Description: TODO
* @Date: 2021/7/8 17:45
*/
@Api(tags = "用户管理模块")
@RestController
@RequestMapping("/user")
public class UserController {
@Value("${shiro.expireTime}")
private int expireTime;
@Autowired
private UserService userService;
@ApiOperation(value = "用户登录", notes =
"请求信息: \n" +
"  loginName : 登录账号 \n" +
"  password : 登录密码 \n" +
"返回码: \n" +
"  200 : 成功 \n" +
"  500 : 服务器错误 ")
@PostMapping("/login")
public HashMap<String, String> login(@RequestBody User user) {
//用户主体信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getLoginName(),
user.getPassword()
);
HashMap<String, String> hashMap = new HashMap<>();
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
} catch (UnknownAccountException e) {
hashMap.put("msg", "登录账号或密码错误,请重新输入!");
return hashMap;
} catch (AuthenticationException e) {
hashMap.put("msg", "授权失败!");
return hashMap;
}
// 登录成功后设置过期时间 单位毫秒
subject.getSession().setTimeout(expireTime * 60 * 1000);
return hashMap;
}
/**
* 访问需求认证的资源路径,默认跳转登录页面,GET请求方式
*
* @return
*/
@GetMapping("/login")
public HashMap<String, String> loginGet() {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("msg","请先进行登录!");
return hashMap;
}
/**
* 退出登录
*
* @return
*/
@ApiOperation(value = "退出登录")
@GetMapping("/logout")
public HashMap<String, String> logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("msg", "退出登录成功!");
return hashMap;
}
/**
* 没有授权跳转请求
*
* @return
*/
@ApiOperation(value = "没有资源授权")
@GetMapping("/unauth")
public HashMap<String, String> unauth() {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("msg","你没有该资源的访问权限!");
return hashMap;
}
@GetMapping("/testAuth")
@RequiresPermissions("test:auth:test")
public String testAuth(){
return "[测试授权]权限";
}
@GetMapping("/testQueryAuth")
@RequiresPermissions("system:button:query")
public String testAuth1(){
return "[查询按钮]权限";
}
}