简单轻量级登录认证鉴权应用(springboot+thymeleaf+apache shiro) - Go语言中文社区

简单轻量级登录认证鉴权应用(springboot+thymeleaf+apache shiro)


最近抽空研究了目前比较常用的轻量级安全框架Apache shiro,首先查看了很多现有的博客,打好理论基础,然后学以致用,码代码出学习成果,收获颇丰!

推荐本人参考过的优秀博客

####参考博客

SpringBoot+shiro整合学习之登录认证和权限控制

https://www.cnblogs.com/ll409546297/p/7815409.html

https://www.cnblogs.com/expiator/p/8651798.html

####参考博客

当然每个人的兴趣点不同,所以对技术性的博客有自己的选择,不过我初入门一门新技术时,都是从理论先入手,搞清楚“是什么?能干什么?”,然后再实践加深理解。本篇博客主要介绍springboot+thymeleaf+apache shiro实践应用,关于shiro的理论基础,小伙伴们自行学习了。

shiro应用主要包含db table、realm、shiroConfig、shiroController、html登录等几部分的创建和编码。

1.首先创建数据表,之前要理清登录用户、角色和权限的关系。一个应用有多个用户,每个用户有多个角色(管理员、游客等),每个角色具有多个权限(查看、添加、删除等)。我这里可提供的建表参考如下,小伙伴可根据自己的情况添加一些数据以备后面测试用:

CREATE TABLE `u_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称',
  `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号',
  `pswd` varchar(32) DEFAULT NULL COMMENT '密码',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
  `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

CREATE TABLE `u_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '角色名称',
  `type` varchar(10) DEFAULT NULL COMMENT '角色类型',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

CREATE TABLE `u_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(256) DEFAULT NULL COMMENT 'url地址',
  `name` varchar(64) DEFAULT NULL COMMENT 'url描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

CREATE TABLE `u_user_role` (
  `uid` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `rid` bigint(20) DEFAULT NULL COMMENT '角色ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `u_role_permission` (
  `rid` bigint(20) DEFAULT NULL COMMENT '角色ID',
  `pid` bigint(20) DEFAULT NULL COMMENT '权限ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.在pom.xml中添加Apache shiro依赖

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

3.realm--认证、鉴权逻辑编码,另外重写了凭据比对器

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

    @Autowired
    private UUserService uUserService;

    /**
     * 第一步:登录认证,用户名、密码和数据库比对
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        List<UUserVo> userVoList = uUserService.findUserByUsername(username);//查询数据库
        if(userVoList==null || userVoList.size()==0){
            throw new AccountException("帐号或密码不正确!");
        }
        UUserVo user = userVoList.get(0);
        if(user.getStatus()==0){
            throw new DisabledAccountException("该账号禁止登录!");
        }

        return new SimpleAuthenticationInfo(user, user.getPswd(), this.getName());
    }

    /**
     * 第二步:角色、权限认证
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        UUserVo user = (UUserVo) principalCollection.getPrimaryPrincipal();

        Set<String> permissions = new HashSet<>();//权限去重
        Set<String> roles = new HashSet<>();//角色去重

        List<UUserVo> userVoList = uUserService.findUserByUsername(user.getEmail());
        for (UUserVo userVo : userVoList){
            roles.add(userVo.getRole());
            permissions.add(userVo.getPermission());
        }

        logger.info("logback: 角色="+roles);
        logger.info("logback: 权限="+permissions);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }
    
}

 其中,主要的dao用户对象查询逻辑如下:

public interface UUserMapper extends Mapper<UUser> {

    @Select("SELECT u.id, u.nickname, u.email, u.pswd, u.`status`, r.`name` as role, p.`name` as permission" +
            " FROM u_user u inner join u_user_role ur on ur.uid=u.id" +
            " inner join u_role r on r.id=ur.rid" +
            " inner join u_role_permission rp on rp.rid=r.id" +
            " inner join u_permission p on p.id=rp.pid" +
            " WHERE u.email=#{username}")
    List<UUserVo> findUserByUsername(@Param("username") String username);
}

其中,重写了凭据比对器 

/**
 * 凭据比对器
 * 重写doCredentialsMatch()
 */
public class CredentialMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String inputPswd = new String(((UsernamePasswordToken)token).getPassword());
        String dbPswd = (String) info.getCredentials();
        return this.equals(inputPswd, dbPswd);
    }
}

 

4.shiro config类至关重要,其中最为重要的是过滤器配置,详细代码如下:

@Configuration
public class ShiroConfig {
    private static Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
    /**
     * 认证(用户名、密码、权限)逻辑
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm realm = new MyShiroRealm();
        return realm;
    }
    /**
     * 安全管理器,shiro的核心组件,管理所有subject
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    /**
     * 处理资源拦截
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setLoginUrl("/shiro/goLogin");//登录认证页面
        shiroFilterFactoryBean.setSuccessUrl("/shiro/goSuccess");//认证成功跳转页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/goError");//未授权页面

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置不会被拦截的静态资源路径
        filterChainDefinitionMap.put("/static/**", "anon");
        //ajax登录url不被拦截
        filterChainDefinitionMap.put("/shiro/login", "anon");
        //退出登录
        filterChainDefinitionMap.put("/shiro/logout", "logout");

        filterChainDefinitionMap.put("/shiro/goAdd", "perms[add]");//权限名称需一致

        //对所有用户认证
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        logger.info("logback: shiro拦截器工厂类注入完成!");
        return shiroFilterFactoryBean;

    }


}

5.shiro controller控制器类编码,其中最主要的是登录逻辑、退出逻辑,其他的都是页面跳转

@Controller
@RequestMapping("/shiro")
public class ShiroController {


    /**
     * ajax form login
     * @param username
     * @param password
     * @return
     * @throws Exception
     */
    @RequestMapping("/login")
    @ResponseBody
    public ResultResponse login(@RequestParam(value = "username", required = true) String username,
                                @RequestParam(value = "password", required = true) String password) throws Exception{
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        Subject subject = SecurityUtils.getSubject();
        subject.login(token);//登录认证、鉴权

        return ResultResponse.ok("login success");
    }

    @RequestMapping("/goLogin")
    public String goLogin(){
        return "login";
    }

    @RequestMapping("/goSuccess")
    public String goSuccess(){
        return "success";
    }

    @RequestMapping("/goError")
    public String goError(){
        return "error";
    }
    //添加权限页面
    @RequestMapping("/goAdd")
    public String goAdd(){
        return "add";
    }

    @RequestMapping("/logout")
    @ResponseBody
    public Object logout(){
        try {
            //退出 跳转到登录页面
            SecurityUtils.getSubject().logout();
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        return "退出登录";
    }
    
}

6.应用的resources目录如下图,涉及到静态资源(js、css)在static目录下,html页面在templates目录下

resources

7. 贴下login.html页面的代码

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>登录</title>
		<!-- boostrap css -->
		<link rel="stylesheet" th:href="@{/static/boostrap/css/bootstrap.min.css}">
		<!-- sweetalert css -->
		<link rel="stylesheet" th:href="@{/static/sweetAlert/dist/sweetalert2.min.css}" />
		<!-- jquery -->
		<script th:src="@{/static/js/jquery-3.4.0.min.js}" src=""></script>
		<!-- bootstrap js -->
		<script th:src="@{/static/boostrap/js/bootstrap.min.js}"></script>
		<!-- sweetalert js -->
		<script th:src="@{/static/sweetAlert/dist/sweetalert2.min.js}"></script>
		<!-- md5 js -->
		<script th:src="@{/static/js/md5.min.js}"></script>
		<script th:src="@{/static/js/common.js}"></script>

		<style>

			.center {
				margin: auto;
				width: 50%;
				margin-top: 200px;
			}

		</style>
	</head>
	<body>
		<div class="container">
			<form class="form-horizontal center" role="form">
				<div class="form-group">
					<label for="mobile" class="col-md-2 control-label">账号</label>
					<div class="col-md-10">
						<input type="text" class="form-control" id="mobile" placeholder="请输入账号">
					</div>
				</div>
				<div class="form-group">
					<label for="password" class="col-md-2 control-label">密码</label>
					<div class="col-md-10">
						<input type="password" class="form-control" id="password" placeholder="请输入密码">
					</div>
				</div>

				<div class="form-group">
					<div class="col-md-offset-2 col-md-10">
						<button type="button" class="btn btn-success btn-block" id="to_login">登录</button>
					</div>
				</div>
			</form>

		</div>

		<script>
			$(function() {
				$("#to_login").click(function() {
					var account = $("#mobile").val();
					var inputPass = $("#password").val();
					if (app.notEmpty(account) && app.notEmpty(inputPass)) {
						//var salt = app.formSalt;
						//var str = "" + salt.charAt(0) + salt.charAt(1) + inputPass + salt.charAt(3) + salt.charAt(4);
						//var password = md5(str);
						// 登录认证
						$.ajax({
							url: "/shiro/login",
							type: "POST",
							dataType:"json",
							data: {
								username: account,
								password: inputPass
							},
							success: function(data) {
								if(data.status==200){
								    //alert(data.data);
                                    //app.littleTip(data.data);
									location.href="/shiro/goSuccess";
								}else{
									//app.littleTip(data.msg);
                                    location.href="/shiro/goError";
								}
							},
							error: function(xhr,errorType) {
								app.littleTip("fail:"+xhr.readyState+","+xhr.status);
							}
						});
					} else {
						app.littleTip("账号或密码不能为空")
					}

				});

			})
		</script>

	</body>
</html>

8.实践过程中的感悟和大家分享

(1)ShiroConfig类中配置过滤器要根据自己的实际需要,本文中配置的较为简单,感觉这部分对于初学者来说稍有难度,不过多测试思考即可发现问题,理解会加深!

(2)thymeleaf下html访问static静态资源方法需注意。最初没有加上“/static/”前缀是行不通的。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/gesanghua601/article/details/90605798
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-08 13:24:34
  • 阅读 ( 1108 )
  • 分类:Go应用

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢