spring集成shiro实现权限认证和自动登录

在登录入口类中先创建用户名/密码身份验证Token。

UsernamePasswordToken token = new UsernamePasswordToken(phone, password);

然后调用subject.login(token);此时SecurityManager将会委托Authenticator进行身份验证,Authenticator会将token传入Realm从Realm获取身份验证信息,如果没有返回或抛出异常表示身份验证失败。用户登录成功可以将用户身份信息放入shiroSession中,在自定义的sessionExpiredFilter类中通过判断请求的session中是否存在用户信息,处理过期的登录请求。(浏览器每次请求会将sessionId通过request传给服务端,通过sessionId获取当前用户的session信息)

  Subject subject = SecurityUtils.getSubject();
        User user = userService.getUserByPhone(phone);
        //创建用户名、密码身份验证Token
        UsernamePasswordToken token = new UsernamePasswordToken(phone, password);
        String loginToken = new Md5Hash(new Date().toString() + ShiroSession.getId()).toHex();

        try {
            subject.login(token);
            ShiroSession.set("user_info", JSON.toJSONString(user));
            //如果登录成功且设置了7天自动登录
            if (remember == true) {
                userService.updateToken(user.getPhone(), loginToken);
                //设置客户端cookie保存7天
                CookieUtil.addCookie(response, "loginToken", loginToken, 7);
            }
            return ResponseUtils.buildOKResponse(JSON.toJSONString(new User(user.getId(), user.getNickName())));

UserRealm用户权限认证:

UserRealm继承AuthorizingRealm类,实现两个方法doGetAuthenticationInfo和doGetAuthorizationInfo
在doGetAuthenticationInfo中从通过
String phone = (String) token.getPrincipal();获得角色信息
String password = new String((char[]) token.getCredentials());获得登录密码
然后与从数据库中查询出的用户密码进行匹配,进行判断逻辑处理,无误后将用户的登录信息存入SimpleAuthenticationInfo并返回:

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
phone,
password,
ByteSource.Util.bytes(phone),//设置username、pwd都没问题
getName() //realm name(唯一)
);
return info;

在doGetAuthorizationInfo类中主要实现对已认证的用户分配资源权限:
String phone = (String) principalCollection.getPrimaryPrincipal();获得认证用户,然后通过自定义的getResourcesByUserId方法获取用户资源列表,放入HashSet中,最后调用SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(new HashSet<>(permissions));将集合内容填充到认证中

ResourceCheckFilter访问路径的认证

ResourceCheckFilter继承AccessControlFilter实现两个方法isAccessAllowed和onAccessDenied,负责访问路径的认证判断:

@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)
            throws Exception {
        Subject subject = getSubject(servletRequest, servletResponse);
        String url = getPathWithinApplication(servletRequest);
        return subject.isPermitted(url);
}

isAccessAllowed方法通过获取request的请求路径,调用isPermitted方法,判断该资源是否可用,如果返回true,走到下一个过滤器。如果没有下一个过滤器的话,表示具有了访问某个资源的权限,如果返回 false,则会调用 onAccessDenied 方法,去实现相应的当过滤不通过的时候执行的操作。
onAccessDenied方法对没有访问权限的页面做处理:

@Override
@ResponseBody
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse)
            throws Exception {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (WebUtil.isAjax(request)) {
            //返回403 FORBIDDEN状态码
            response.setStatus(403);
        }
        //如果是普通请求,进行页面重定向
        else {
            response.sendRedirect(request.getContextPath()+"/403");
        }
        return false;
    }

UrlPermissionResolver解析权限字符串

实现PermissionResolver接口,它负责解析权限字符串到Permission实例,RolePermissionResolver用于根据角色解析相应的权限集合。
WildcardPermission是shiro默认的权限字符串的表示方式,此处根据我们自己的url格式自定义UrlPermission类进行权限匹配。
UrlPermission 是一个实现了Permission接口的类,它的implies方法的实现决定了权限是否匹配。

public class UrlPermissionResolver implements PermissionResolver {
    @Override
    public Permission resolvePermission(String s) {
        if (s.startsWith("/")) {
            return new UrlPermission(s);
        } else {
            return new WildcardPermission(s);
        }
    }
}

class UrlPermission implements Permission {
    private String url;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public UrlPermission() {
    }

    public UrlPermission(String url) {
        this.url = url;
    }

    @Override
    public boolean implies(Permission permission) {
        if (!(permission instanceof UrlPermission)) {
            return false;
        }
        UrlPermission urlPermission = (UrlPermission) permission;
        PatternMatcher patternMatcher = new AntPathMatcher();
        return patternMatcher.matches(this.getUrl(), urlPermission.getUrl());
    }
}

spring-shiro.xml配置文件




    
    
    
    

    
        
        
        
        
    

    
        
        
        
        
        
        
    

    
    
        
        
        
        
            
            
                /assets/** = anon

                /admin/**= sessionExpiredFilter,user,resourceFilter
                
                
            
        
    

    
    
    

    
    
        
    

    
    
        
    


可以看到上面spring-shiro的shiroFilter拦截器中还定义了一个sessionExpiredFilter,在该过滤器中我们实现自己的自动登录逻辑和登录过期请求的处理:

首先在用户登录认证代码中如果用户登录成功,且选择了自动登录选项,我们保存用户的登录状态信息到session中,然后生成一个loginToken认证码,更新到user表的字段中:

String loginToken = new Md5Hash(new Date().toString() + ShiroSession.getId()).toHex();
      try {
            subject.login(token);
            ShiroSession.set("user_info", JSON.toJSONString(user));
            //如果登录成功且设置了7天自动登录
            if (remember == true) {
                userService.updateToken(user.getPhone(), loginToken);
                //设置客户端cookie保存7天
                CookieUtil.addCookie(response, "loginToken", loginToken, 7);
            }
            return ResponseUtils.buildOKResponse(JSON.toJSONString(new User(user.getId(), user.getNickName())));

}catch(){

}

SessionExpiredFilter过滤器

在自定义的SessionExpiredFilter过滤器中写http请求的前置拦截逻辑代码,先判断session中用户的登录状态,若不存在则用户未登录,然后判断request请求中是否存在loginToken(未选择自动登录或者已过7天token失效),若存在,通过该token值从数据库中查询与之匹配的用户,若存在,则执行登录代码,若不存在,则执行逻辑跳转处理:

public class SessionExpiredFilter extends PathMatchingFilter {
    @Autowired
    UserService userService;

    @Override
    protected boolean onPreHandle(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (ShiroSession.get("user_info") != null) {
            return true;
        } else {
            //判断请求端带过来cookie是否存在
            String loginToken = CookieUtil.findCookieByName(request, "loginToken");
            if (StringUtils.isNotBlank(loginToken)) {
                //到数据库查询有没有该Cookie
                User user = userService.findUserByToken(loginToken);
                if (user != null) {
                    try {
                        //重新登录
                        Subject subject = SecurityUtils.getSubject();
                        UsernamePasswordToken token = new UsernamePasswordToken(user.getPhone(), user.getPassword());
                        subject.login(token);
                        ShiroSession.set("user_info", JSON.toJSONString(user));
                        return true;
                    } catch (AuthenticationException e) {
                        e.printStackTrace();
                        return false;
                    }
                } else {
                    //没有该Cookie与之对应的用户(Cookie不匹配)
                    CookieUtil.clearCookie(request, response, "loginToken");
                    return false;
                }
            } else {
                //没有登录,也没有cookie凭证
                if (WebUtil.isAjax(request)) {
                    response.setStatus(401);
                } else {
                    response.sendRedirect(request.getContextPath() + "/401");
                }
                return false;
            }
        }
    }
}

在此过程中尝试过使用UserInterceptor,在拦截器中处理登录过期请求,但是shiroFilter过滤器的处理顺序在Spring的拦截器之前执行,处理请求无法到达interceptor。

配置中的注入的shiroFilter在web.xml文件中通过DelegatingFilterProxy对servlet filter的代理,来实现自定义的filter使配置的shiro拦截器生效。

web.xml配置文件


    

    
        shiroFilter
        org.springframework.web.filter.DelegatingFilterProxy
        
            targetFilterLifecycle
            true
        
    
    
        shiroFilter
        /*
    

你可能感兴趣的:(spring集成shiro实现权限认证和自动登录)