SpringBoot集成安全认证框架Shiro的简单方法,能有效区分RestAPI 接口与web页面的不同处理
本文介绍在SpringBoot2.6下配置Shiro认证的方法:
1.pom.xml引入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
2.定义 MyAuthorizingRealm 实现用户的认证和权限提取,绑定自己业务的用户服务对象进行处理
public class MyAuthorizingRealm extends AuthorizingRealm { @Autowired private UserService userService = null; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); Set<String> permList = new HashSet<>(); //TODO 读取权限的方法,一般通过用户获取角色,读取角色所有拥有的权限,比如user:list,user:save这种标记,存储到permList中。 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); if(permList!=null) info.setStringPermissions(permList); return info; } //自行编码处理验证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String)token.getPrincipal(); String password = new String((char[])token.getCredentials()); if(StringUtils.isEmpty(username)) { throw new UnknownAccountException(); } User user = userService.findByUserName(username); if(user == null){ throw new UnknownAccountException(); } if(password.equals(user.getUserPassword())) { throw new IncorrectCredentialsException(); } return new SimpleAuthenticationInfo(username, password, getName()); }
3.定义Shiro配置,将自定义的realm对象进行绑定(此处还有更多的配置,比如sessionManager,cacheManager等)
@Configuration
public class ShiroConfiguration {
//将自己的验证方式加入容器
@Bean
public CmsAuthorizingRealm myShiroRealm() {
MyAuthorizingRealmmyShiroRealm = new MyAuthorizingRealm();
return myShiroRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//登录页
shiroFilterFactoryBean.setLoginUrl("/login.html");
return shiroFilterFactoryBean;
}
//加入注解的使用,不加入这个注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
4.登录方法,定义LoginController进行登录登出(CommonResult是自定义的一个返回对象)
@PostMapping("/login")
@ResponseBody
public CommonResult<String> login(@RequestParam("userName") String userName,
@RequestParam("password") String password, HttpServletRequest request,ModelMap model){
//做简单的登录的操作
UsernamePasswordToken token = new UsernamePasswordToken(userName,password);
Subject subject = SecurityUtils.getSubject();
try {
//登录操作,该方法没有抛出异常表示登录成功,接下来就可以根据业务方法获取用户对象,构建session
subject.login(token);
User user = userService.findByUserName(userName);
//设置session
request.getSession().setAttribute("user",user);
return CommonResult.success("");
}
catch(Exception ex){
log.error("登录错误",ex);
return CommonResult.error(1,"用户名密码错误");
}
5.登出方法
@RequestMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
Subject subject = SecurityUtils.getSubject();
subject.logout();
request.getSession().setAttribute("user", null);
response.sendRedirect("/");
}
6.给方法加上授权认证
此时虽然加了shiro登录认证,但每个controller方法并没有加上权限、认证等检查,需要加上需要对应的权限或认证注解即可
要求必须登录,但不检查具体权限:
@RequiresAuthentication
@GetMapping(value = "/list")
public CommonResult<List<User>> getUserList(@RequestParam("ids") Collection<Integer> ids) {
return CommonResult.success(userService.findAllById(ids));
}
@GetMapping(value = "/get")
public CommonResult<User> get(Integer userId){
return CommonResult.success(userService.find(userId));
}
此时页面请求get时不需要登录,请求list需要登录才能访问。
下面的get请求必须有content:mgr权限才能够访问,每个用户拥有的权限列表是在MyAuthorizingRealm 加入到permList中的。也可以通过RequiresRoles来检查角色。
@RequiresPermissions("content:mgr")
@RequestMapping("get")
public void get(HttpServletRequest request,HttpServletResponse response) {
...
}
但此时无论是rest接口还是请求的页面,在没有登录时都会跳转到登录界面,rest接口一般希望返回json无权限的提示好让调用方进行处理,页面可以跳转到登录页。
7、定义Shiro异常,针对请求类型返回不同的内容
页面控制器一般用Controller定义,rest接口控制器一般用RestController定义,针对两种Controller进行拦截处理
注意比较关键:其中的Order(1)注解,让RestController优先于Controller的handler先加载,否则因为继承关系,RestController的Handler会不生效。
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
@Order(1)
public class RestControllerExceptionHandler {
@ExceptionHandler({Exception.class})
public CommonResult handException(Exception e) {
log.error("",e);
return CommonResult.error(500,e.toString());
}
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
public CommonResult handAuthException(Exception e) {
//log.error("",e);
return CommonResult.error(401,e.getMessage());
}
}
处理Controller页面请求的授权检查异常:
@Slf4j
@ControllerAdvice(annotations = Controller.class)
@Order(2)
public class ControllerExceptionHandler {
@ExceptionHandler(value = Exception.class)
public String handException(Exception e) {
log.error("",e);
return "error";
}
@ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class })
public String handAuthException(Exception e) {
//log.error("",e);
//对于页面为授权的直接返回到登录页
return "login";
}
}
这两个handler也支持通过package进行匹配,如下:
@ControllerAdvice(basePackages = "com.xxxx.page")
这样满足com.xxx.page下的Controller都会进行匹配进行异常的处理,这样就不用配置Order注解了。
其他配置方法说明:
在第三步的shiroFilterFactoryBean上可以增加正则匹配方式的filter,对url进行权限的校验:
Map<String,String> map = new HashMap<String, String>();
map.put("/console/_login","anon"); //anon不需要检查
map.put("/console/login","authc"); //认证方法
/**
* 路径 -> 过滤器名称1[参数1,参数2,参数3...],过滤器名称2[参数1,参数2...]...
* 自定义配置(前面是路径, 后面是具体的过滤器名称加参数,多个用逗号进行分割,过滤器参数也多个之间也是用逗号分割))
* 有的过滤器不需要参数,比如anon, authc, shiro 在解析的时候接默认解析一个数组为 [name, null]
*/
/
//绑定具体的角色,权限,没经过验证
map.put("/user/**", "roles[系统管理员,用户管理员],perms['user:manager:*']");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
浙公网安备 33010602011771号