SpringBoot学习(四)--集成shiro实现基础登陆认证和权限管理

来源:互联网 发布:杀戮汉化 知乎 编辑:程序博客网 时间:2024/06/05 08:07

版权声明:作者原创,转载请注明出处。

SpringBoot学习系列目录:

  • SpringBoot学习(一)–构建第一个SpringBoot工程
  • SpringBoot学习(二)–集成mybatis+freemark
  • SpringBoot学习(三)–Spring profile多环境方式实现logback日志配置

简介

Shiro是Apache的一个开源安全框架,旨在简化身份验证和授权,主要用来处理身份认证,授权,企业会话管理和加密等,并且Shiro不依赖任何容器在JavaSE和JavaEE项目中都可以使用。
相比较Spring家族的Spring Security,Shiro在保持强大功能的同时,还具有简单灵活轻量等特点,学习成本也要低很多,目前受到越来越多项目系统的应用,更多关于shiro的介绍可以自己百度,这里不偏题。

实现

以下为SpringBoot集成Shiro,实现基础登陆认证和权限管理的过程。
一.引入依赖文件

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

注意有需要的可以同时引入shiro-ehcache实现缓存,我这边因为后面会集成使用redis,所以就不引入了。引入shiro-ehcache也并不复杂,多加一个配置可自行百度,对于分布式部署来说,没有必要使用shiro-ehcache。

二.基础表
这里介绍使用简单的用户+权限的表结构,需要三张表,用户表,角色表,以及关系表,建表sql如下

create table CMS_USER_INFO(   ID              integer(10) not null auto_increment comment '用户ID',   USER_CODE            varchar(20) not null comment '用户编码',   USER_NAME            varchar(64) not null comment '用户名称',   USER_PWD             varchar(150) comment '用户密码',   REMARK               varchar(60) comment '备注',   CREATE_BY            varchar(20) comment '创建人',   CREATE_DATE          datetime comment '创建时间',   MODIFIED_BY          varchar(20) comment '修改人',   MODIFIED_DATE        datetime comment '修改时间',   SORTNO               integer(2) default 0 comment '排序',   STATE                integer(2) comment '数据状态',   primary key (ID));create table CMS_ROLE_INFO(   ID                   integer(10) not null auto_increment comment '角色编号',   ROLE_CODE            varchar(20) not null comment '角色编码',   ROLE_NAME            varchar(60) not null comment '角色名称',   CREATE_BY            varchar(20) comment '创建人',   CREATE_DATE          datetime comment '创建时间',   MODIFIED_BY          varchar(20) comment '修改人',   MODIFIED_DATE        datetime comment '修改时间',   SORTNO               integer(2) default 0 comment '排序',   STATE                integer(2) comment '数据状态',   primary key (ID));create table CMS_USER_ROLE_R(   ID                   int not null auto_increment,   USER_CODE            varchar(10),   ROLE_CODE            varchar(10),   CREATE_BY            varchar(20) comment '创建人',   CREATE_DATE          datetime comment '创建时间',   MODIFIED_BY          varchar(20) comment '修改人',   MODIFIED_DATE        datetime comment '修改时间',   SORTNO               integer(2) default 0 comment '排序',   STATE                integer(2) comment '数据状态',   primary key (ID));

测试数据如下

INSERT INTO `cms_user_info` VALUES (1, 'admin', '管理员', 'd0970714757783e6cf17b26fb8e2298f', '测试备注', 'admin', '2017-12-8 20:32:02', 'admin', '2017-12-8 20:32:09', 0, 1);INSERT INTO `cms_user_info` VALUES (2, '17040406', '张三', 'd0970714757783e6cf17b26fb8e2298f', '测试备注', 'admin', '2017-12-8 20:32:02', 'admin', '2017-12-8 20:32:09', 1, 1);INSERT INTO `cms_role_info` VALUES (1, 'admin', '系统管理员', 'admin', '2017-12-8 19:06:35', 'admin', '2017-12-8 19:06:39', 0, 1);INSERT INTO `cms_role_info` VALUES (2, 'guest', '客人', 'admin', '2017-12-8 19:06:35', 'admin', '2017-12-8 19:06:35', 1, 1);INSERT INTO `cms_user_role_r` VALUES (1, 'admin', 'admin', 'admin', '2017-12-8 20:19:34', 'admin', '2017-12-8 20:19:34', 0, 1);INSERT INTO `cms_user_role_r` VALUES (2, '17040406', 'guest', 'admin', '2017-12-8 20:19:34', 'admin', '2017-12-8 20:19:34', 0, 1);

三.配置shiro
SpringBoot中集成shiro时,因为省去了配置文件,所以需要编写配置类,使用@Configuration注解注入配置类。这里最简洁shiro的配置需要包含三个方法:
1.注册Realm至Spring Bean,Realm是认证和授权的具体实现,shiro对于使用者来说需要实现的一个方法,它的简洁之处也在于只需要实现这个接口便可使用shiro。
2.注册SecurityManager安全管理器,无特殊需求使用默认管理器便可,SecurityManager是shiro的主入口,该配置方法将用户自定义的Realm注入SecurityManager即可。
3.配置Filter访问策略,这里请看代码,相信详细注解一看就明白。如下:

package com.pf.org.cms.common;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.realm.Realm;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;/** * @Auther: pf * @Date: 2017/12/12 19:34 * @Description: shiro配置组件 */@Configurationpublic class ShiroConfiguration {    private static final Logger log = LoggerFactory.getLogger(ShiroConfiguration.class);    /**     * 注入Realm     * @return MyRealm     */    @Bean(name = "myRealm")    public MyRealm myAuthRealm() {        MyRealm myRealm = new MyRealm();        log.info("myRealm注册完成");        return myRealm;    }    /**     * 注入SecurityManager     * @param myRealm     * @return SecurityManager     */    @Bean(name = "securityManager")    public SecurityManager securityManager(@Qualifier("myRealm")MyRealm myRealm) {        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();        manager.setRealm(myRealm);        log.info("securityManager注册完成");        return manager;    }    /**     * 注入Filter     * @param securityManager     * @return ShiroFilterFactoryBean     */    @Bean(name = "shiroFilter")    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();        filterFactoryBean.setSecurityManager(securityManager);        // 配置登录的url和登录成功的url        filterFactoryBean.setLoginUrl("/auth/login");        filterFactoryBean.setSuccessUrl("/home");        // 配置未授权跳转页面        filterFactoryBean.setUnauthorizedUrl("/errorPage/403");        // 配置访问权限        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();        filterChainDefinitionMap.put("/css/**", "anon"); // 表示可以匿名访问        filterChainDefinitionMap.put("/fonts/**", "anon");        filterChainDefinitionMap.put("/imgs/**", "anon");        filterChainDefinitionMap.put("/js/**", "anon");        filterChainDefinitionMap.put("/auth/**", "anon");        filterChainDefinitionMap.put("/errorPage/**", "anon");        filterChainDefinitionMap.put("/demo/**", "anon");        filterChainDefinitionMap.put("/swagger-*/**", "anon");        filterChainDefinitionMap.put("/swagger-ui.html/**", "anon");        filterChainDefinitionMap.put("/webjars/**", "anon");        filterChainDefinitionMap.put("/v2/**", "anon");        filterChainDefinitionMap.put("/admin/**", "roles[admin]");// 表示admin权限才可以访问,多个加引号用逗号相隔        filterChainDefinitionMap.put("/*", "authc");// 表示需要认证才可以访问        filterChainDefinitionMap.put("/**", "authc");        filterChainDefinitionMap.put("/*.*", "authc");        filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);        log.info("shiroFilter注册完成");        return filterFactoryBean;    }}

注意shiro支持配置路径,页面标签,注解等等多种权限配置方式,和不同粒度的权限配置。
1.配置方式:

默认过滤器(10个)   anon -- org.apache.shiro.web.filter.authc.AnonymousFilter  authc -- org.apache.shiro.web.filter.authc.FormAuthenticationFilter  authcBasic -- org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter  perms -- org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter  port -- org.apache.shiro.web.filter.authz.PortFilter  rest -- org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter  roles -- org.apache.shiro.web.filter.authz.RolesAuthorizationFilter  ssl -- org.apache.shiro.web.filter.authz.SslFilter  user -- org.apache.shiro.web.filter.authc.UserFilter  logout -- org.apache.shiro.web.filter.authc.LogoutFilter  anon:例子/admins/**=anon 没有参数,表示可以匿名使用。   authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数   roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。   perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。   rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。   port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。   authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证   ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https   user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查   

2.注解方式

  • RequiresAuthentication:使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
  • RequiresGuest:使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
  • RequiresPermissions:当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
  • RequiresRoles:当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
  • RequiresUser:当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。

3.页面标签:博主使用较少,应用场景也比较少,可以自己百度,这里不做介绍。

四.编写MyRealm
正如第三点所说,因为shiro并不知道你登陆认证的具体逻辑和授权的具体逻辑,所以需要用户自己实现,继承AuthorizingRealm,实现doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(登陆认证)两个抽象方法,具体代码如下:

package com.pf.org.cms.common;import com.pf.org.cms.entity.UserInfo;import com.pf.org.cms.entity.UserRoleInfo;import com.pf.org.cms.service.UserService;import com.pf.org.cms.utils.MD5Util;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.util.ByteSource;import org.springframework.beans.factory.annotation.Autowired;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @Auther: pf * @Date: 2017/12/12 19:29 * @Description: 认证和授权具体实现 */public class MyRealm extends AuthorizingRealm {    @Autowired    private UserService userService;    /**     * 为当前subject授权     * @param principalCollection     * @return AuthorizationInfo     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        Map<String, Object> params = new HashMap<>();        params.put("userCode", (String) super.getAvailablePrincipal(principalCollection));        List<UserRoleInfo> userRoleInfos = userService.getUserRoleInfos(params);        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();        if(!userRoleInfos.isEmpty()) {            for(UserRoleInfo role : userRoleInfos) {                info.addRole(role.getRoleCode());            }        }        return info;    }    /**     * 认证登陆subject身份     * @param authenticationToken     * @return AuthenticationInfo     * @throws AuthenticationException     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;        Map<String, Object> params = new HashMap<>();        params.put("userCode", (String)authenticationToken.getPrincipal());        List<UserInfo> userInfos = userService.getUserInfos(params);        if (userInfos.isEmpty()) {            throw new UnknownAccountException();        } else if(userInfos.size() > 1) {            throw new DisabledAccountException();        } else {            UserInfo user = userInfos.get(0);            // 校验密码            return new SimpleAuthenticationInfo(authenticationToken.getPrincipal(), user.getUserPwd(), ByteSource.Util.bytes("2w@W"),  getName());        }    }}

五.登陆和授权验证
下面是登陆的controller:

package com.pf.org.cms.web;import com.pf.org.cms.common.IConstants;import com.pf.org.cms.entity.JsonBean;import com.pf.org.cms.utils.ParamUtils;import org.apache.commons.collections.MapUtils;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.DisabledAccountException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;import java.util.Map;/** * @Auther: pf * @Date: 2017/12/12 19:41 * @Description: */@Controller@RequestMapping(value = "/auth")public class AuthenticationController {    private static final Logger log = LoggerFactory.getLogger(AuthenticationController.class);    @RequestMapping(value = "/login")    public String login() {        return "/login";    }    @ResponseBody    @RequestMapping(value = "/login_in", produces = "application/json;charset=UTF-8")    public JsonBean loginIn(HttpServletRequest request) {        JsonBean reJson = new JsonBean();        Map paramMap = ParamUtils.handleServletParameter(request);        String userCode = MapUtils.getString(paramMap, "userCode");        String userPwd = MapUtils.getString(paramMap, "userPwd");        // shiro认证        Subject subject = SecurityUtils.getSubject();        UsernamePasswordToken token = new UsernamePasswordToken(userCode, userPwd);        try {            subject.login(token);        } catch (UnknownAccountException e) {            reJson.setMessage("账户不存在");            return reJson;        } catch (DisabledAccountException e) {            reJson.setMessage("账户存在问题");            return reJson;        } catch (AuthenticationException e) {            reJson.setMessage("密码错误");            return reJson;        } catch (Exception e) {            log.info("登陆异常", e);            reJson.setMessage("登陆异常");            return reJson;        }        reJson.setStatus(IConstants.RESULT_INT_SUCCESS);        String res = subject.getPrincipals().toString();        if (subject.hasRole("admin")) {            res = res + "----------你拥有admin权限";        }        if (subject.hasRole("guest")) {            res = res + "----------你拥有guest权限";        }        reJson.setData(res);        reJson.setMessage("登陆成功");        return reJson;    }}

页面:

<html><head>    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    <#include "/common/common.ftl"/></head><body><div>    <span>hello! please login!</span></div><div>    <span>用户名:</span><input type="text" id="userCode" />    <span>密码:</span><input type="text" id="userPwd" /></div><div>    <input type="button" onclick="submitLogin()" value="登陆"></div></body></html><script type="text/javascript" src="/js/md5.js"></script><script type="text/javascript">    function submitLogin() {        var userCode = $.trim($("#userCode").val());        var userPwd = $.trim($("#userPwd").val());        var hexPwd = hex_md5(userPwd);        $.ajax({            type: 'POST',            url: "http://localhost:8080/auth/login_in",            data: {"userCode":userCode,"userPwd":hexPwd},            dataType:"json",            success: function(rep){                if(rep.status == 0){                    alert(rep.message);                    alert(rep.data);                    //window.location.reload();//                    loadPage(0,baseUrl);                }else{                    alert(rep.message);                }            },            error:function(rep){                alert("获取信息失败!");            }        });    }</script>

编写完成后启动SpringBoot工程,访问不允许匿名访问的路径,会跳转到登陆页面,登陆成功后可查看自己所拥有的权限,同样可以访问相关路径测试是否授权成功。具体代码可以访问github(https://github.com/15651037763/cms)下载。

写在最后,这里介绍的只是最简单方式的使用,以上github地址为本系列文章项目地址,本人能力有限博客略有延迟,更多shiro功能可以关注github或持续更新,在学习和研究的同时,博客会做到尽量总结全面不遗漏。

阅读全文
1 0
原创粉丝点击