首页 > 其他分享 >SpringBoot集成安全认证框架Shiro的简单方法,能有效区分RestAPI 接口与web页面的不同处理

SpringBoot集成安全认证框架Shiro的简单方法,能有效区分RestAPI 接口与web页面的不同处理

时间:2022-11-05 10:00:11浏览次数:55  
标签:web return SpringBoot 登录 RestAPI class new CommonResult public

本文介绍在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注解了。

标签:web,return,SpringBoot,登录,RestAPI,class,new,CommonResult,public
From: https://www.cnblogs.com/webjlwang/p/16855741.html

相关文章