Spring boot后台搭建二集成Shiro实现用户验证

上一篇文章中介绍了Shiro 查看

将Shiro集成到spring boot的步骤:

  (1)定义一个ShiroConfig,配置SecurityManager Bean,SecurityManager为Shiro的安全管理器,管理着所有Subject

  (2)在ShiroConfig中配置ShiroFilterFactoryBean,其为Shiro过滤器工厂类,依赖于SecurityManager

  (3)自定义Realm实现,Realm包含doGetAuthorizationInfo()doGetAuthenticationInfo()方法

实现下用户认证:

  没登录跳转到登录页面;登录后跳转到首页面;注销后,跳转到登录页面

1.引入依赖

  引入Shiro和thymeleaf依赖

<!-- thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

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

2.ShiroConfig

   定义一个Shiro配置类,ShiroConfig

package com.sfn.bms.common.shiro;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.*;

/**
 * Shiro 配置类
 */
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录的url
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后跳转的url
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权url
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 定义filterChain,静态资源不拦截
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/fonts/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        // druid数据源监控页面不拦截
        filterChainDefinitionMap.put("/druid/**", "anon");
        // 配置退出过滤器,其中具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/", "anon");
        // 除上以外所有url都必须认证通过才可以访问,未通过认证自动访问LoginUrl
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

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

    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm shiroRealm = new MyShiroRealm();
        return shiroRealm;
    }

}

  anonauthc等为Shiro为实现的过滤器

3.Realm

  Realm进行实现,然后注入到SecurityManager中

  自定义Realm实现只需继承AuthorizingRealm类,然后实现doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可

  用户认证只实现doGetAuthenticationInfo()

package com.sfn.bms.common.shiro;
import com.sfn.bms.system.model.User;
import com.sfn.bms.system.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 自定义实现 ShiroRealm,包含认证和授权两大模块
 */
@Component("shiroRealm")
public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 授权模块,获取用户角色和权限
     *
     * @param principal principal
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {

        return null;
    }

    /**
     * 用户认证
     *
     * @param token AuthenticationToken 身份认证 token
     * @return AuthenticationInfo 身份认证信息
     * @throws AuthenticationException 认证相关异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        // 获取用户输入的用户名和密码
        String account = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());

        // 通过用户名到数据库查询用户信息
        User user = userService.findByAccount(account);

        if (user == null) {
            throw new UnknownAccountException("用户名或密码错误!");
        }
        if (!password.equals(user.getPassword())) {
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        if (user.getStatus().equals("0")) {
            throw new LockedAccountException("账号已被锁定,请联系管理员!");
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
        return info;
    }


}

4.数据层

  数据表User

  实体类User

package com.sfn.bms.system.model;

import javax.persistence.*;

public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Short id;

    /**
     * 账号
     */
    private String account;

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

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

    /**
     * 状态 1-正常,0-禁用,-1-删除
     */
    private Boolean status;

    /**
     * 添加时间
     */
    @Column(name = "create_time")
    private Integer createTime;

    /**
     * 上次登陆时间
     */
    @Column(name = "last_login_time")
    private Integer lastLoginTime;

    /**
     * 上次登录IP
     */
    @Column(name = "last_login_ip")
    private String lastLoginIp;

    /**
     * 登陆次数
     */
    @Column(name = "login_count")
    private Integer loginCount;

    /**
     * @return id
     */
    public Short getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Short id) {
        this.id = id;
    }

    /**
     * 获取账号
     *
     * @return account - 账号
     */
    public String getAccount() {
        return account;
    }

    /**
     * 设置账号
     *
     * @param account 账号
     */
    public void setAccount(String account) {
        this.account = account == null ? null : account.trim();
    }

    /**
     * 获取密码
     *
     * @return password - 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     *
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    /**
     * 获取邮箱
     *
     * @return email - 邮箱
     */
    public String getEmail() {
        return email;
    }

    /**
     * 设置邮箱
     *
     * @param email 邮箱
     */
    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }

    /**
     * 获取状态 1-正常,0-禁用,-1-删除
     *
     * @return status - 状态 1-正常,0-禁用,-1-删除
     */
    public Boolean getStatus() {
        return status;
    }

    /**
     * 设置状态 1-正常,0-禁用,-1-删除
     *
     * @param status 状态 1-正常,0-禁用,-1-删除
     */
    public void setStatus(Boolean status) {
        this.status = status;
    }

    /**
     * 获取添加时间
     *
     * @return create_time - 添加时间
     */
    public Integer getCreateTime() {
        return createTime;
    }

    /**
     * 设置添加时间
     *
     * @param createTime 添加时间
     */
    public void setCreateTime(Integer createTime) {
        this.createTime = createTime;
    }

    /**
     * 获取上次登陆时间
     *
     * @return last_login_time - 上次登陆时间
     */
    public Integer getLastLoginTime() {
        return lastLoginTime;
    }

    /**
     * 设置上次登陆时间
     *
     * @param lastLoginTime 上次登陆时间
     */
    public void setLastLoginTime(Integer lastLoginTime) {
        this.lastLoginTime = lastLoginTime;
    }

    /**
     * 获取上次登录IP
     *
     * @return last_login_ip - 上次登录IP
     */
    public String getLastLoginIp() {
        return lastLoginIp;
    }

    /**
     * 设置上次登录IP
     *
     * @param lastLoginIp 上次登录IP
     */
    public void setLastLoginIp(String lastLoginIp) {
        this.lastLoginIp = lastLoginIp == null ? null : lastLoginIp.trim();
    }

    /**
     * 获取登陆次数
     *
     * @return login_count - 登陆次数
     */
    public Integer getLoginCount() {
        return loginCount;
    }

    /**
     * 设置登陆次数
     *
     * @param loginCount 登陆次数
     */
    public void setLoginCount(Integer loginCount) {
        this.loginCount = loginCount;
    }
}
View Code

  UserService 

package com.sfn.bms.system.service;

import com.sfn.bms.common.service.IService;
import com.sfn.bms.system.model.User;

public interface UserService extends IService<User> {
    User findByAccount(String account);
}
View Code

  UserServiceImpl

package com.sfn.bms.system.service.impl;

import com.sfn.bms.common.service.impl.BaseService;
import com.sfn.bms.system.model.User;
import com.sfn.bms.system.service.UserService;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.entity.Example;

import java.util.List;

@Repository("userService")
public class UserServiceImpl extends BaseService<User> implements UserService {
    @Override
    public User findByAccount(String account) {
        Example example = new Example(User.class);
        example.createCriteria().andCondition("lower(account)=", account.toLowerCase());
        List<User> list = this.selectByExample(example);
        return list.isEmpty() ? null : list.get(0);
    }

}
View Code

  UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sfn.bms.system.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.sfn.bms.system.model.User">
    <!--
      WARNING - @mbg.generated
    -->
    <id column="id" jdbcType="SMALLINT" property="id" />
    <result column="account" jdbcType="VARCHAR" property="account" />
    <result column="password" jdbcType="CHAR" property="password" />
    <result column="email" jdbcType="VARCHAR" property="email" />
    <result column="status" jdbcType="BIT" property="status" />
    <result column="create_time" jdbcType="INTEGER" property="createTime" />
    <result column="last_login_time" jdbcType="INTEGER" property="lastLoginTime" />
    <result column="last_login_ip" jdbcType="VARCHAR" property="lastLoginIp" />
    <result column="login_count" jdbcType="INTEGER" property="loginCount" />
  </resultMap>

</mapper>
View Code

5.前端页面

  login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/css/login.css}" type="text/css">
    <link rel="stylesheet" th:href="@{css/iCheck/minimal/blue.css}" type="text/css">
    <link rel="stylesheet" th:href="@{css/app.css}" type="text/css">
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
<div class="login-page">
    <!-- Form-->
    <div class="form">
        <div class="form-toggle"></div>
        <div class="form-panel one">
            <div class="form-header">
                <h1>账户登录</h1>
            </div>
            <div class="form-content">
                <div class="form-group">
                    <label>用户名</label>
                    <input type="text" name="account" />
                </div>
                <div class="form-group">
                    <label>密码</label>
                    <input type="password" name="password" />
                </div>
                <div class="form-group">
                    <button onclick="login()" id="loginButton">登录</button>
                </div>
            </div>
        </div>
    </div>

</div>
</body>
<script th:inline="javascript">
    var ctx = [[@{/}]];
        function login() {
            var account= $("input[name='account']").val();
            var password = $("input[name='password']").val();
            $.ajax({
                type: "post",
                url: ctx + "login",
                data: {"account": account,"password": password},
                dataType: "json",
                success: function (r) {
                    if (r.code == 0) {
                        location.href = ctx + 'index';
                    } else {
                        alert(r.msg);
                    }
                }
            });
        }
</script>
</html>

  index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>你好![[${user.account}]]</p>
    <a th:href="@{/logout}">注销</a>
</body>
</html>
View Code

6.Controller方法

LoginController
package com.sfn.bms.system.controller;

import com.sfn.bms.common.domian.ResponseBo;
import com.sfn.bms.common.util.MD5Utils;
import com.sfn.bms.system.model.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class LoginController {

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

    @PostMapping("/login")
    @ResponseBody
    public ResponseBo login(String account, String password) {
        // 密码MD5加密
        password = MD5Utils.encrypt(account, password);
        System.out.println(password);
        UsernamePasswordToken token = new UsernamePasswordToken(account, password);
        // 获取Subject对象
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return ResponseBo.ok();
        } catch (UnknownAccountException e) {
            return ResponseBo.error(e.getMessage());
        } catch (IncorrectCredentialsException e) {
            return ResponseBo.error(e.getMessage());
        } catch (LockedAccountException e) {
            return ResponseBo.error(e.getMessage());
        } catch (AuthenticationException e) {
            return ResponseBo.error("认证失败!");
        }
    }

    @RequestMapping("/")
    public String redirectIndex() {
        return "redirect:/index";
    }

    @RequestMapping("/index")
    public String index(Model model) {
        // 登录成后,即可通过Subject获取登录的用户信息
        User user = (User) SecurityUtils.getSubject().getPrincipal();

        model.addAttribute("user", user);
        return "index";
    }
}

7.测试

  启动项目

  浏览器打开

  http://localhost:8080/

  http://localhost:8080/index

  回跳转到http://localhost:8080/login

  输入用户名super密码123456

点注销,根据ShiroConfig的配置filterChainDefinitionMap.put("/logout", "logout"),Shiro会自动帮我们注销用户信息,并重定向到/路径

相关代码 地址

posted @ 2019-06-21 13:20  慕尘  阅读(614)  评论(0编辑  收藏  举报