Spring Boot Shiro(二)用户认证(附源码)

本文基于springboot2.1.1.RELEASE

Spring Boot Shiro用户认证

在Spring Boot中集成Shiro进行用户的认证过程主要可以归纳为以下三点:

1、定义一个ShiroConfig,然后配置SecurityManager Bean,SecurityManager为Shiro的安全管理器,管理着所有Subject;

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

3、自定义Realm实现,Realm包含doGetAuthorizationInfo()和doGetAuthenticationInfo()方法,因为本文只涉及用户认证,所以只实现doGetAuthenticationInfo()方法。

引入依赖

搭建一个Spring Boot Web程序,然后引入Shiro、MyBatis、数据库和thymeleaf依赖:

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- shiro-spring -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- druid数据源驱动 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

ShiroConfig

定义一个Shiro配置类,名称为ShiroConfig

@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(){  
        // 配置SecurityManager,并注入shiroRealm
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        return securityManager;  
    }  
	
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        // Shiro生命周期处理器
        return new LifecycleBeanPostProcessor();
    }
	
    @Bean  
    public ShiroRealm shiroRealm(){  
        // 配置Realm,需自己实现
        ShiroRealm shiroRealm = new ShiroRealm();  
        return shiroRealm;  
    }  
}

配置完ShiroConfig后,接下来对Realm进行实现,然后注入到SecurityManager中。

Realm

自定义Realm实现只需继承AuthorizingRealm类,然后实现doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可。这两个方法名乍看有点像,authorization发音[ˌɔ:θəraɪˈzeɪʃn],为授权,批准的意思,即获取用户的角色和权限等信息;authentication发音[ɔ:ˌθentɪ’keɪʃn],认证,身份验证的意思,即登录时验证用户的合法性,比如验证用户名和密码。

public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserMapper userMapper;
    
    /**
    * 获取用户角色和权限
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        return null;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

    	// 获取用户输入的用户名和密码
        String userName = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
        
        System.out.println("用户" + userName + "认证-----ShiroRealm.doGetAuthenticationInfo");

        // 通过用户名到数据库查询用户信息
        User user = userMapper.findByUserName(userName);
        
        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;
    }
}

因为本节只讲述用户认证,所以doGetAuthorizationInfo()方法先不进行实现。

其中UnknownAccountException等异常为Shiro自带异常,Shiro具有丰富的运行时AuthenticationException层次结构,可以准确指出尝试失败的原因。你可以包装在一个try/catch块,并捕捉任何你希望的异常,并作出相应的反应。例如:

try {
    currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... catch your own ...
} catch ( AuthenticationException ae ) {
    //unexpected error?
}

接下来就是创建测试,数据库字段如下:
库表对应的实体类:

public class User implements Serializable{

    private static final long serialVersionUID = -5440372534300871944L;
    
    private Integer id;
    private String userName;
    private String password;
    private Date createTime;
    private String status;
    
    // get,set略
}

定义接口UserMapper:

@Mapper
public interface UserMapper {
    User findByUserName(String userName);
}

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.springboot.dao.UserMapper">
    <resultMap type="com.springboot.pojo.User" id="User">
        <id column="id" property="id" javaType="java.lang.Integer" jdbcType="NUMERIC"/>
        <id column="username" property="userName" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <id column="password" property="password" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <id column="create_time" property="createTime" javaType="java.util.Date" jdbcType="DATE"/>
        <id column="status" property="status" javaType="java.lang.String" jdbcType="VARCHAR"/>
    </resultMap>
    <select id="findByUserName" resultMap="User">
        select * from t_user where username = #{userName}
    </select>
</mapper>

Controller

@Controller
public class LoginController {

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

    @PostMapping("/login")
    @ResponseBody
    public ResponseBo login(String username, String password) {
    	// 密码MD5加密
        password = MD5Utils.encrypt(username, password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, 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";
    }
}

需注意:

在这里插入图片描述

因为springboot2.1.1.RELEASE默认mysql 版本为8点几,所以用 com.mysql.cj.jdbc.Driver

如果你配置低版本用com.mysql.jdbc.driver驱动

登录成功后,根据之前在ShiroConfig中的配置shiroFilterFactoryBean.setSuccessUrl("/index"),页面会自动访问/index路径

结构如下:

在这里插入图片描述

源码:
https://pan.baidu.com/s/18osCwdNi0ZwpCbT7jkzi4g

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值