无配置文件,极简shiro整合JFinal

  1. 目前从网上找到的绝大部分基本上都是基于spring或者使用spring的方式和shiro整合的方式方法,都需要在web.xml等配置 shiro的过滤器 来接管所有请求。我这边的主要需求就是想使用shiro的sessioin管理组件,权限校验组件等核心功能组件。
    2.本着既然使用了jfinal,而且interceptor本身就很强大了,就不要再让别的去接管请求处理。去spring思维,去配置文件,不增加繁杂代码,低侵入性,灵活手工调用,易于插拔,28原则,使用核心功能。

    编写了一个小插件,来使用shiro的核心功能。如果后期想去掉使用系统自带功能,基本无成本。
    3.插件的文件就2个类 ShiroPlugin,ShiroUtil.
    直接上代码:

     

3.1).ShiroPlugin
根据jfinal插件规范编写

public class ShiroPlugin implements IPlugin{
 
 /**
  * shiro的核心控制器,大脑 
  */
 private DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
 /**
  * 设置session管理器 为 DefaultWebSessionManager(否则默认为ServletContainerSessionManager)
  */
 private DefaultWebSessionManager sessionManager =new DefaultWebSessionManager();
 /**
  * 单位毫秒 全局的 session过期时间,默认就是30分钟(1800000)
  */
 private long globalSessionTimeout = 1800000L;
 /**
  *  与tomcat等web容器的默认的cookie中的键值 JSESSIONID冲突(ShiroHttpSession,中常量默认值JSESSIONID)
  *  会导致session的覆盖或丢失等问题
  */
 private final String SESSION_ID_NAME = "WEB_SESSIONID";
 
 public ShiroPlugin(){
 }
 
 public ShiroPlugin(Realm realm) {
  securityManager.setRealm(realm);
 }
 
 public ShiroPlugin(Collection<Realm> realms) {
  securityManager.setRealms(realms);
 }
 //如果通过ini文件来设置realm 那么需要配置
 public ShiroPlugin(String iniPath) { 
  IniRealm iniRealm = new IniRealm(iniPath);
        securityManager.setRealm(iniRealm);
 }
 
 private void init(){
  Cookie cookie = new SimpleCookie(SESSION_ID_NAME);
  sessionManager.setSessionIdCookie(cookie);
  sessionManager.setGlobalSessionTimeout(globalSessionTimeout);
  //sessionManager.setCacheManager(cacheManager);缓存管理器 TODO
  securityManager.setSessionManager(sessionManager);
  //securityManager.setCacheManager(cacheManager);缓存管理器 TODO
  
  SecurityUtils.setSecurityManager(securityManager);
 }
 @Override
 public boolean start() {
  init();
  return true;
 }
 @Override
 public boolean stop() {
  return true;
 }
 
 public DefaultSecurityManager getSecurityManager() {
  return securityManager;
 }
 public void setSecurityManager(DefaultSecurityManager securityManager) {
  this.securityManager = securityManager;
 }
 public DefaultWebSessionManager getSessionManager() {
  return sessionManager;
 }
 public void setSessionManager(DefaultWebSessionManager sessionManager) {
  this.sessionManager = sessionManager;
 }
 public long getGlobalSessionTimeout() {
  return globalSessionTimeout;
 }
 public void setGlobalSessionTimeout(long globalSessionTimeout) {
  this.globalSessionTimeout = globalSessionTimeout;
 }
 
}

3.2).ShiroUtil
提供一些便捷的基本方法

public class ShiroUtil {
 
 public static Subject getSubject(){
  return SecurityUtils.getSubject();
 }
 
 public static Object getUser(){
  return getSubject().getPrincipal();
 }
 
 public static Session getSession(){
  return SecurityUtils.getSubject().getSession();
 }
 
 public static boolean hasRole(String roleIdentifier){
  return getSubject().hasRole(roleIdentifier);
 }
 
 public static boolean hasAllRole(Collection<String> roleIdentifiers){
  return getSubject().hasAllRoles(roleIdentifiers);
 }
 
 public static boolean hasAnyRole(Collection<String> roleIdentifiers){
  boolean hasAnyRole = false;
        for (String role : roleIdentifiers) {
                if(hasRole(role)){
                 hasAnyRole = true;
                 break;
                }
        }
        return hasAnyRole;
 }
 
 public static boolean hasPermit(String permitIdentifier){
  return getSubject().isPermitted(permitIdentifier);
 }
 
 public static boolean hasAllPermit(String... permits){
  return getSubject().isPermittedAll(permits);
 }
 
 public static boolean hasAnyPermit(String... permits){
  boolean hasAnyPermit = false;
        for (String permit : permits) {
                if(hasPermit(permit)){
                 hasAnyPermit = true;
                 break;
                }
        }
        return hasAnyPermit;
 }
 public static void refreshSessionLastAccessTime(){
  try {
   getSession().touch();
        } catch (Throwable t) {
            LogKit.error("session.touch() method invocation has failed.", t);
        }
 }
 public static SecurityManager getSecurityManager(){
  return  SecurityUtils.getSecurityManager();
 }
 
 
}

4. 使用方式:
4.1.启动配置

public void configPlugin(Plugins me) {

  ........

  ........

  ShiroPlugin sp = new ShiroPlugin(new 自定义的Realm对象());

  me.add(sp);

  ..........

  ..........

 }

4.2. 定义一个interceptor
 手动调用shiro进行校验(在一般spring项目中都是过滤器自动实现了这些校验过程,在spring和传统的在web.xml中配置filter等方式,会走很多层过滤器,然后根据配置的登录url,失败url,无权限url进行跳转。个人感觉很繁琐,不如在代码里面直接控制。
 因为这里使用的JFinal,基本上项目中都是用自定义的若干interceptor来实现我们的拦截和过滤。这个拦截器仅使用了shiro的 验证是否登录和验证权限是否拥有,shiro其他强大功能未使用。

public class LoginShiroInterceptor implements Interceptor{ 
 @Override
 public void intercept(Invocation inv) {
  Controller controller = inv.getController();
  //对request和response进行包装
  ShiroHttpServletRequest   request = new ShiroHttpServletRequest(inv.getController().getRequest(), JFinal.me().getServletContext(), true);
  ShiroHttpServletResponse response = new ShiroHttpServletResponse(inv.getController().getResponse(),JFinal.me().getServletContext(),request);
  //手动创建 Subject 对象
  Subject subject =(new WebSubject.Builder(request,response)).buildWebSubject();
  //绑定 subject 并 刷新session最后访问时间,延长session过期时间
  ThreadContext.bind(subject);
  ShiroUtil.refreshSessionLastAccessTime();
  //核心逻辑
  if(subject.isAuthenticated()){
   LogKit.info("已经登陆");
   if(ShiroUtil.hasRole("我是角色标识")){
    //拥有该角色放行或者其他逻辑
   }
   // 例如 通过 inv.getActionKey() 是否一致判断拥有该权限;subject拥有的角色和权限都已经在自定义的realm的 doGetAuthorizationInfo中定义好了。
   if(ShiroUtil.hasPermit("我是权限标识")){
    //拥有该权限放行或者其他逻辑
   } 
  }else{
   LogKit.info("未登录");
   //跳转登录或者其他逻辑处理
  } 
 }
 //TODO 对jwt等无状态服务进行验证的逻辑拦截 
}

4.3提供一个 简单的 自定义realm 示例

public class AuthRealm extends AuthorizingRealm{
 UserService userService = Enhancer.enhance(UserService.class);
 
 public AuthRealm() {
 }
 public AuthRealm(CredentialsMatcher credentialsMatcher) {
  super(credentialsMatcher);
 }
 
 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  String userName =  token.getPrincipal().toString();
  String password =  new String((char[])token.getCredentials());
 
  SysUser user = userService.获取用户(username,password);
  // 账号不存在
  if (user == null) {
   throw new UnknownAccountException("账号或密码不正确");
  }
  // 账号锁定
  if ("2".contentEquals(user.getAccountState())) {
   throw new LockedAccountException("账号已被锁定,请联系管理员");
  }
  SimpleAuthenticationInfo authticInfo = new SimpleAuthenticationInfo(user, password, getName());
  return authticInfo;
 }
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  SysUser user = (SysUser)ShiroUtil.getUser();
  Set<String> rolesSet = userService.获取用户所有角色集合(user)
  Set<String> permsSet = userService.获取用户所有权限集合(user)
  SimpleAuthorizationInfo authorizinfo = new SimpleAuthorizationInfo();
  authorizinfo.setRoles(rolesSet);
  authorizinfo.setStringPermissions(permsSet);
  return authorizinfo;
 }

}
4.5 提供一个 简单的登录controller 的 登录方法示例 
 

@Log("登录")

 public void doLoginUseShiro(){

  String userName  =  ...

  String password  =  ...

  String yzm = ... 

  if(validateCaptcha("yzm")){//先验证验证码 

    UsernamePasswordToken token = new UsernamePasswordToken(userName,password);

    token.setRememberMe(true);

    ShiroHttpServletRequest request = new ShiroHttpServletRequest( getRequest(), JFinal.me().getServletContext(), true);

    ShiroHttpServletResponse response = new ShiroHttpServletResponse( getResponse(),JFinal.me().getServletContext(),request); 

    Subject subject =(new WebSubject.Builder(request,response)).buildWebSubject();

       ThreadContext.bind(subject);

    subject.login(token); 

    this.renderJson(ret);

  }else{

   this.renderJson(ret.setFail().set("msg", "验证码错误"));

  }

 }


 

评论区

JFinal

2019-03-25 20:45

chance_xym

2019-11-21 19:39

这里UserService获取根据用户名和密码获取用户对象,密码还是没加密过的吧,一般的程序,是不是会把密码做盐值加密,然后存入数据库,然后从数据库里取出user对象啊?
楼主的思路很好,很需要啊,我正在学习和测试。感谢感谢

chance_xym

2019-11-21 19:43

还有,想知道getSubject().getPrincipal()和user对象之间能做强转,这个user类,有什么要求吗?

chance_xym

2019-11-21 21:39

亲,你这个里面需要配置shiro.ini吗?如果不需要的话,我想问问,类似这样的 /=anon /img/**=anon 要怎么配置啊?

Terely

2019-11-24 20:56

@chance_xym 你这个是不是 想要 放行不拦截? 直接@Clear 就行了吧。
user类强转就是你放入的类型就不会报类型转换异常了。

来自星星的猫教授

2020-01-08 20:16

超赞,很好用!谢谢楼主。

水货

2020-02-18 10:26

AuthRealm里面的doGetAuthenticationInfo和doGetAuthorizationInfo都不会被调用,插件配置类里面有ShiroPlugin sp = new ShiroPlugin(new ShiroAuthRealm()); 拦截器配置类里面也确认有me.add(new ShiroAuthInterceptor());

水货

2020-02-18 11:13

无视吧,真是丢脸啊,哎

steven_lhcb_9527

2021-01-05 13:40

Enhancer这个类我看应该过时了,@JFinal @Terely ,应该用什么Aop.inject()吗

JFinal

2021-01-05 14:02

@steven_lhcb_9527 注入用 @Inject 或者 Aop.inject, 远比 Ehnahcer 要好用要强大

如果是要创建对象并且要注入,用 Aop.get(...)

steven_lhcb_9527

2021-01-06 09:38

@chance_xym @水货 感觉代码有点问题,doGetAuthenticationInfo和doGetAuthorizationInfo没有地方能调用。然后拦截器直接走到了未登录那块