Springboot整合Shiro实现登录登出

一、准备工作

1.1新建用户表

DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
  `id` int(64) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL,
  `salt` varchar(64) DEFAULT NULL,
  `state` varchar(8) DEFAULT NULL,
  `username` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

插入一条数据: 这里使用Md5加密  加盐值,后文会将该工具类代码贴出

INSERT INTO `user_info` VALUES ('1', '管理员', '565111f370e4dd497aa4eeb985efa6a3', '7mrNoL/keN6rbqZDx+PKyg==', '1', 'admin');

1.2整体结构

 

 1.3pom.xml   

<properties>
        <java.version>1.8</java.version>
        <shiro.version>1.4.2</shiro.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <!--SpringBoot热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

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

        <!--Shiro核心框架 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
        </dependency>

        <!-- Shiro使用Srping框架 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>

 1.4 application.properties  记得替换为自己的

spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username = 账号
spring.datasource.password = 密码

mybatis.mapper-locations=classpath:/mappers/*.xml

1.5 User类

public class User {

    private Integer id;

    private String name;

    private String password;

    private String salt;

    private String state;

    private String username;

    geetter... setter...
    toString...

1.6 UserService与UserServiceImpl

public interface UserService {

    User getByUserName(String username);
}
@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public User getByUserName(String username) {
        return  userMapper.getByUserName(username);
    }
}

1.7 UserMapper 与UserMapper.xml

@Mapper
public interface UserMapper {

    User getByUserName(String username);
}
<?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.example.demo3.mapper.UserMapper">
    <sql id="Base_Column_List">
        id,name,password,salt,state,username
    </sql>
    
    <select id="getByUserName" parameterType="java.lang.String" resultType="com.example.demo3.domain.User">
        select
         <include refid="Base_Column_List"/>
        from user_info where username = #{username}
    </select>
</mapper>

1.8 LoginController

@Controller
public class LoginController {

    private static Logger logger = LoggerFactory.getLogger(LoginController.class);

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

    @RequestMapping("/doLogin")
    public String doLogin(String username,String password,Model model){
        //添加用户认证信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                username,
                password);
        //进行验证,这里可以捕获异常,然后返回对应信息
        //默认登陆成功后跳转的是刚才访问的页面
        try {
            subject.login(usernamePasswordToken);
            if (subject.isAuthenticated()){
                return "redirect:/index";
            }else{
                usernamePasswordToken.clear();
                return "redirect:/login";
            }
        }catch (UnknownAccountException e){
          model.addAttribute("msg","用户名不存在");
          return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

    @RequestMapping("/index")
    public String toIndex(Model model){
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        model.addAttribute("user",user);
        return "index";
    }
}

1.9  相关页面

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>登录页</title>
    <style>
        .error{color: red}
    </style>
</head>
<body>
<h1>Shiro登录页</h1>
<form id="loginForm" action="/doLogin" method="post">
    <p th:text="${msg}" class="error"></p>
    <input type="text" name="username" placeholder="输入用户名"/>
    <input type="password" name="password" placeholder="输入密码"/>
    <button type="submit">登录</button>
</form>
</body>
</html>

 index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>欢迎!<p th:text="${user.name}"></p></h1>
<a href="/logout">安全退出</a>
</body>
</html>

二、Shiro相关配置

2.1 自定义域  CustomRealm    

执行授权部分其实是要需要查询登录用户的角色与菜单权限的,本文只实现登录登出,则省略

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

    @Autowired
    UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.debug("######## Shiro执行授权 ########");
        //能进入这里表示账号已经通过验证了
        User user = (User) principalCollection.getPrimaryPrincipal();
        if (null != user){
            //获取用户的角色与权限
            //Set<String> roleNames = roleService.listRoleNames(user.getUserid());
           // Set<String> perNames = permissionService.listPermissionNames(user.getUserid());
            //授权对象
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //把角色与权限放进去
            //info.setRoles(roleNames);
            //info.setStringPermissions(perNames);
            return info;
        }
        logger.debug("****授权失败****用户信息为空!");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.debug("######## Shiro执行认证 ########");
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //获取用户输入的账号
        String username = upToken.getUsername();
        System.out.println("####"+username);
        //获取数据库中的密码
        User user = userService.getByUserName(username);

        user = userService.getByUserName(username);
        if (null == user){
            return null;
        }
        String passwordInDB = user.getPassword();
        String salt = user.getSalt();
        //把当前用户的认证信息存入SimpleAuthenticationInfo 并返回
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,passwordInDB, 
        ByteSource.Util.bytes(salt),getName());
        return info;
    }
}

2.2 Shiro配置类 ShiroConfiguration

@Configuration
public class ShiroConfiguration {

    @Bean(name = "customRealm")
    public CustomRealm myShiroRealm(HashedCredentialsMatcher matcher){
        CustomRealm myShiroRealm= new CustomRealm();
        //设置密码加密
        myShiroRealm.setCredentialsMatcher(matcher);
        return myShiroRealm;
    }

    //把realm注册到securityManager中
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(HashedCredentialsMatcher matcher){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm(matcher));
        return securityManager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> map = new HashMap<String, String>();
        //把处理登录的路径放行
        map.put("/doLogin","anon");
        //登出
        map.put("/logout","logout");
        //对所有用户认证
        map.put("/**","authc");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //加入注解的使用,不加入这个注解不生效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

}

三、效果展示

登录页输入账号:admin   密码:123   点击登录

四、安全退出

4.1 LoginController中新增

    @RequestMapping("/logout")
    public String doLogOut(Model model){
     Subject subject = SecurityUtils.getSubject();
     subject.logout();
     model.addAttribute("msg", "安全退出!");
     return "login";
    }

4.2 效果展示

五、扩展

  • Shiro配置类中的 setSuccessUrl 认证成功后跳转路径,但是系统默认登录成功后首次跳转的地址为访问系统时初次使用地址, 比如说我们首次访问的是登录页面,那这里登录成功后它还是会跳转到登录页面。 解决这个的方法有好几种,我这里是在subject.login之后,根据subject.isAuthenticated()验证当前用户是否登录来进行重定向
  • Shiro的退出登录其实是不需要在Controller中写的,只要拦截到访问/logout的请求,就会被走logout对应的 LogoutFilter,自动登出,并且也会清理用户的session信息
  • logout 无参,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url

 

加密工具:

public class PasswordEncryptionUtil {
    public static void main(String[] args) {
        String password = "123";
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        //String salt = "3SdRKRjauah+nwPf7g/VFQ==";
        int times = 2;
        String algorithmName = "md5";

        String encodedPassword = new SimpleHash(algorithmName,password,salt,times).toString();
        System.out.printf("原始密码是 %s , 盐是: %s, 运算次数是: %d, 运算出来的密文是:%s ",password,salt,times,encodedPassword);
    }

    public static Map<String, String> doEncrypt(String password,String salt){
        Map<String, String> resultMap = new HashMap<>();
        if (StringUtils.isBlank(salt)){
            salt = new SecureRandomNumberGenerator().nextBytes().toString(); //盐量随机
        }
        //两次Md5加密
        String encodedPassword= new SimpleHash("md5",password,salt,2).toString();
        resultMap.put("salt",salt);
        resultMap.put("encodedPassword",encodedPassword);
        return resultMap;
    }
}

demo已上传码云:https://gitee.com/wanglonewalker/demo3.git

 

 

 

 

 

 

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LoneWalker、

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值