springboot+shiro简单的权限管理

shiro是我接触关于安全认证这块最早的东西,以前单纯的写代码没深入的时候(非科班出身)就对这个感到很新奇,简简单单的对web.xml多了个配置(以前的web项目里),就能实现用户接口进行拦截,实用又简单,这次就正好是有了一定了解后对它进行一次自我总结,不好或者有片面的可以留言我来补充:

  一.理解shiro的基础就需要先了解什么是filter(过滤器),而理解过滤器就需要了解过滤器和拦截器的区别,自我总结2点差别如       下:

1.拦截器是基于java反射机制的,过滤器是基于函数回调;过滤器依赖serlevt容器,拦截器依赖bean对象;导致拦截器可以调用内部任意接口(但自身限制在controller层),而过滤器只在serlevt容器初始化的时候调用一次(过滤适用于所有匹配的接口)

2假设serlevt应用服务是tomcat,那么对应的关系是:过滤器在前,然后是serlevt容器创建后,然后才是拦截器的种种对接口的限制

3filter常用业务场景都是在对数或者请求地址进行分发路由(如网关),Interceptor(或者通常shandler)

 

不好意思工作中此文章写了一半停了(忙其他的去了),耽误了好几天,更新继续!

 

虽然Apache官网已整合了springboot,但是对实际项目中的一些问题,类如验证码、密码错误次数限制,错误自定义没有明确的说明(也有可能是我没仔细研究查看),感兴趣的自己可以找找 https://shiro.apache.org/spring-boot.html ,我这里就没用整合包,直接实用shiro和spring整合包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

第一步,配置shiroConfig

1创建shiroConfig类,增加shirofilter过滤器

 @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //过滤所有请求(专为前后分离添加的过滤请求)
        filterMap.put("custom", new ShiroUserFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
         // 过滤链定义,从上向下顺序执行,一般将 / ** 放在最为下边:这是一个坑呢,一不小心代码就不好使了;
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login/auth", "anon");
        filterChainDefinitionMap.put("/login/logout", "anon");
        filterChainDefinitionMap.put("/error", "anon");
        // 使用该过滤器过滤所有的链接
        filterChainDefinitionMap.put("/**","custom");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

2加载securityManager的bean

@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm());
    return securityManager;
}

3加载自定义ReaLm类

@Bean
public UserRealm userRealm() {
    UserRealm userRealm = new UserRealm();
    return userRealm;
}

4开启注解所需的bean

/**
 * Shiro生命周期处理器
 */
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
}

/**
 * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
 */
@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;
}

二重写Realm类(doGetAuthorizationInfo验证用户身份,authcToken在登陆时候设定)

public class UserRealm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
 //todo
}
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { //todo
}
}

三.登陆登出方法(其中/login/auth必须在过滤器中设置anon匿名可进入参数)

RestController
@Api(tags = "认证登陆登出接口")
public class LoginController {
@PostMapping("/login/auth")
@ApiOperation(value = "认证接口")
    public Response login(@RequestBody User user) {// 登录测试
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserCode(),user.getLoginPwd());
        try {
            currentUser.login(token);
            return new Response(CodeEnum.SUCCESS, null);
        } catch (IncorrectCredentialsException e) {
            //密码错误
            return new Response(CodeEnum.AUTH_UNMATCH, null);
        }
         catch (UnknownAccountException e) {
            //用户不存在
            return new Response(CodeEnum.USER_IS_NOT_EXISTS, null);
        }
         catch (DisabledAccountException e) {
            //用户已停用
            return new Response(CodeEnum.STOP_USE, null);

        } catch (ExcessiveAttemptsException e) {
            //用户被锁定
            return new Response(CodeEnum.AUTH_LOCKED, null);
        }catch (Exception e){
            //其他错误
            return new Response(CodeEnum.ERR_STATUS, null);
        }

    }
    @ApiOperation(value = "退出接口")
    @GetMapping("/logout")
    public Response logOut() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return new Response(CodeEnum.SUCCESS, null);
    }

}

当 currentUser.login(token)执行到时就调用userRealm类的doGetAuthenticationInfo(AuthenticationToken authcToken)来现实认证功能,具体实现功能根据数据库设计而已,其主要SimpleAuthenticationInfo(认证信息)

       SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userEntity.getUserCode(),
                userEntity.getLoginPwd(),
                ByteSource.Util.bytes(salt),
//                ByteSource.Util.bytes("salt"), //salt=username+salt,  //采用明文访问时,不需要此句
                getName()
        );

因为我这里是将用户大部分信息放在session作为缓存中,所以认证信息只保留了当前用户名

//将用户信息放入session中
SecurityUtils.getSubject().getSession().setAttribute(Constants.SESSION_USER_INFO(自定义一个字符key), userEntity(用户信息));

四,细节问题(个人认为比较大的几个注意问题)

    1自定义异常问题:

doGetAuthenticationInfo(AuthenticationToken authcToken)有AuthenticationException,其下异常可自定义(比如我在登陆的时候就做了catch模块对不同异常不同定义)

  2在将seesion放入用户信息之前,最好将密码信息(如密码,盐等)进行清空

//session中去掉密码和盐,防止泄露

userEntity.setLoginPwd("");

userEntity.setSalt("");

3用户角色信息,可以放在登陆认证中,也可以放在认证拦截器方法时,主要方法:

authorizationInfo.addRoles(roles);

五跨域问题

filterMap.put("custom", new ShiroUserFilter())在拦截器过滤链上增加一个shiroUserFilter过滤器

import com.alibaba.fastjson.JSONObject;
import com.wjx.product.els.config.common.CodeEnum;
import com.wjx.product.els.config.common.Response;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author zhouxl
 * 重写shiro的filter
 */
public class ShiroUserFilter extends UserFilter {

    /**
     * 在访问过来的时候检测是否为OPTIONS请求,如果是就直接返回true
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            setHeader(httpRequest,httpResponse);
            return true;
        }
        return super.preHandle(request,response);
    }

    /**
     * 该方法会在验证失败后调用,这里由于是前后端分离,后台不控制页面跳转
     * 因此重写改成传输JSON数据
     */
    @Override
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        setHeader((HttpServletRequest) request,(HttpServletResponse) response);
        PrintWriter out = response.getWriter();
//       重写没有认证通过返回的状态码和异常
        out.println(JSONObject.toJSONString(new Response<>(CodeEnum.AUTH_NOTLOGIN, null)));
        out.flush();
        out.close();
    }

    /**
     * 为response设置header,实现跨域
     */
    private void setHeader(HttpServletRequest request,HttpServletResponse response){
        //跨域的header设置
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", request.getMethod());
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        //防止乱码,适用于传输JSON数据
        response.setHeader("Content-Type","application/json;charset=UTF-8");
        response.setStatus(HttpStatus.OK.value());
    }

}

 

 


     
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值