SpringBoot+Shiro整合进行登录验证与权限控制

20,451次阅读
22 条评论

共计 5860 个字符,预计需要花费 15 分钟才能阅读完成。

前言

最近在新写项目的时候,有需要用到大量的权限控制的地方,如果自己手动写太耗时了,所以就采用了 Shiro 进行权限控制与登录验证,下面我们来看看如何整合他们。
SpringBoot+Shiro 整合进行登录验证与权限控制

正文

添加依赖

首先,我们先将 maven 依赖添加到项目的 pom.xml 中:

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

添加配置文件

然后添加 Shiro 配置,在 Springboot 中都是采用注解方式进行注册:

@Configuration
public class ShiroConfiguration {private Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){logger.info("ShiroConfiguration initialized");
        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

        // 登出
        filterChainDefinitionMap.put("/logout", "logout");

        //<!-- 过滤链定义,从上向下顺序执行,一般将 /** 放在最为下边 -->: 这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc: 所有 url 都必须认证通过才可以访问; anon: 所有 url 都都可以匿名访问 -->
        filterChainDefinitionMap.put("/admin/**", "authc");
        filterChainDefinitionMap.put("/**", "anon");

        // 如果不设置默认会自动寻找 Web 工程根目录下的 "/login.jsp" 页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


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

    // 开启 shiro aop 注解支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * DefaultAdvisorAutoProxyCreator,Spring 的一个 bean,由 Advisor 决定对哪些类的方法进行 AOP 代理。*/
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }


}

身份效验

接着在 Shiro 配置下新建一个 MyRealm.java,继承 AuthorizingRealm

public class MyRealm extends AuthorizingRealm {private final static Logger logger = LoggerFactory.getLogger(MyRealm.class);

    @Resource
    private UserService userService;
    @Resource
    private RoleService roleService;


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {logger.debug("################## 执行 Shiro 权限认证 ##################");
        User user = (User) principalCollection.getPrimaryPrincipal();
        if(user!=null){SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            Role role = roleService.getById(user.getRole().getRId());
            List<Permission> permissions = role.getPermissions();
            for (Permission p : permissions){info.addStringPermission(p.getPAlias());
            }
            info.addRole(role.getRAlias());
            return info;
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        logger.info("用户验证执行 :"+token.getUsername());
        User user = userService.getByEmail(token.getUsername(),true);
        if(user==null){logger.error("用户 {"+token.getUsername()+"} 不存在");
            throw new AccountException("账户不存在");
        }
        if(user.getStatus()==0){logger.error("用户 {"+token.getUsername()+"} 被禁止登录");
            throw new DisabledAccountException("账号已经禁止登录");
        }else{user.setUpdated(DateUtils.getNowTimestamp());
            user.setUpdatedAt(DateUtils.getNowFormatDate(null));
            System.out.println("效验更新前 ROLE:"+user.getRole().getRId());
            userService.update(user,true,user.getId());
        }
        return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
    }


    @PostConstruct
    public void initCredentialsMatcher() {
        // 该句作用是重写 shiro 的密码验证,让 shiro 用我自己的验证
        setCredentialsMatcher(new CredentialsMatcher());

    }
}
接着,我们还要添加一个密码校验器
public class CredentialsMatcher extends SimpleCredentialsMatcher {private final static Logger LOGGER = LoggerFactory.getLogger(CredentialsMatcher.class);

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {UsernamePasswordToken authcToken = (UsernamePasswordToken) token;
        Object tokenCredentials = EncryptUtils.md5(authcToken.getUsername()+String.valueOf(authcToken.getPassword()));
        Object accountCredentials = getCredentials(info);
        return accountCredentials.equals(tokenCredentials);
    }
}
我这里密码是采用用户名 + 密码进行 MD5 加密进行处理的,你可以根据自己的密码加密方式进行更改;到这一步,Shiro 配置基本上都已经完成了。
下面附上我的用户实体和权限、角色的实体以对应上方的身份验证。
@Data
public class User implements Serializable {
    /**
     * 用户 ID
     */
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 手机号
     */
    private String phone;

    /**
     * 密码
     */
    private String password;

    /**
     * 角色
     */
    private Role role;

    /**
     * 状态:1- 正常,0- 封禁
     */
    private Integer status;

    /**
     * 头像
     */
    private String avatar;

    /**
     * 创建时间
     */
    private Long created;

    /**
     * 修改时间
     */
    private Long updated;

    /**
     * 创建时间字符串
     */
    private String createdAt;

    /**
     * 修改时间字符串
     */
    private String updatedAt;

    private static final long serialVersionUID = 1L;
}
@Data
public class Role implements Serializable {
    /**
     * 角色唯一标识符
     */
    private Integer rId;

    /**
     * 角色名称
     */
    @NotNull(message = "角色名称不能为空")
    private String rName;

    /**
     * 角色别名
     */
    @NotNull(message = "角色别名不能为空")
    private String rAlias;

    /**
     * 创建时间
     */
    private Long created;

    /**
     * 修改时间
     */
    private Long updated;

    /**
     * 创建时间字符串
     */
    private String createdAt;

    /**
     * 修改时间字符串
     */
    private String updatedAt;

    private List<Permission> permissions;


    private static final long serialVersionUID = 1L;


}
@Data
public class Permission implements Serializable {
    /**
     * 权限唯一标识 ID
     */
    private Integer pId;

    /**
     * 权限名称
     */
    private String pName;

    /**
     * 权限别名
     */
    private String pAlias;

    /**
     * 创建时间
     */
    private Long created;

    /**
     * 修改时间
     */
    private Long updated;

    /**
     * 创建时间字符串
     */
    private String createdAt;

    /**
     * 修改时间字符串
     */
    private String updatedAt;

    private static final long serialVersionUID = 1L;
}
PS:在上述实体类代码中采用了 lombok 进行 getter/setter 方法的映射,所以如果要使用以上的实体类请添加 lombok 的依赖到项目中或者自己添加 getter/setter 方法 SpringBoot+Shiro 整合进行登录验证与权限控制
正文完
使用官方微信小程序体验更多功能
post-qrcode
 30
憧憬Licoy
版权声明:本站原创文章,由 憧憬Licoy 于2017-09-10发表,共计5860字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(22 条评论)
未来 评论达人 LV.1
2019-06-21 22:50:32 回复

楼主,主题是什么?能说下吗

 Windows  Chrome  中国河南省安阳市联通
Treasure 评论达人 LV.1
2017-10-24 15:46:43 回复

在身份校验的时候,有这么一行代码:
User user = userService.getByEmail(token.getUsername(),true);
token.getUsername()拿到的类型不匹配,怎么办?不能强转

 Windows  Chrome  中国北京北京市电信
Treasure 评论达人 LV.1
2017-10-24 14:57:50 回复

您的mapper和Dao,service以及controller可以给我看一下么?我纯粹的新手小白,还希望能多看一点萌发一下

 Windows  Chrome  中国北京北京市电信
Treasure 评论达人 LV.1
2017-10-24 11:45:06 回复

刚刚回复为什么没有了,没别的,顶楼主,爱楼主,大爱楼主。通俗易懂,关键字都解释的特别到位,非常适合我这种启蒙小白,希望楼主多多写出这样的代码,给力!赞楼主!

 Windows  Chrome  中国北京北京市电信
    憧憬Licoy 博主
    2017-10-24 11:46:41 回复

    @Treasure 你刚才回复的实际上已经提交到后台了,但是因为页面是缓存了的,所以你刷新之后不会看你的评论,只有在审核通过之后才会自动刷新缓存,这个时候你才可以看见你的评论。

     Windows  Chrome
      Treasure 评论达人 LV.1
      2017-10-24 11:57:38 回复

      @憧憬Licoy 楼主,你的这个写法,对Token运用的不是很多,而且没有验证码校验,你的实际项目能分享一下么?我看到的每一个shiro文章都是不同的实现写法,对于初学shiro的我,晕晕乎乎的

       Windows  Chrome  中国北京北京市电信
Treasure 评论达人 LV.1
2017-10-24 11:43:14 回复

大爱楼主,这种每一行代码都有注释的文章,实在是我这shiro入门小白的爱,通俗易懂,好入门,关键字也解释到位!谢谢楼主分享,支持你的更多作品!

 Windows  Chrome  中国北京北京市电信
Amanda 评论达人 LV.1
2017-09-30 17:41:40 回复

博主,你是做什么技术的呀 ? 是做什么语言开发的呀

 Windows  Chrome  中国河南省郑州市电信
快乐网讯 评论达人 LV.1
2017-09-29 14:48:05 回复

文章不错非常喜欢

 Windows  Chrome  中国广东省佛山市电信
最好的代写essay服务 评论达人 LV.1
2017-09-21 19:59:11 回复

看起来还是比较简单

 Windows  Chrome  乌克兰
夏日博客 评论达人 LV.1
2017-09-10 23:31:59 回复

好复杂的权限控制。

 Windows  Chrome  中国北京北京市联通