spring bootshiro

Springboot整合Shiro:简洁的身份认证

2019-05-23  本文已影响507人  张小黑的猫
shiro.gif
*******完整代码在文章最下面,转载请说明出处,谢谢 *******

简单的web应用进行身份认证的流程:
1.对未认证的用户请求进行拦截,跳转到认证页面。
2.用户通过用户名+密码及其他凭证进行身份认证,认证成功跳转成功页面,认证失败提示相关失败信息。

根据流程,采用shiro进行快速开发。
1.对未认证的用户请求进行拦截,跳转到认证页面。
(0) 这里需要shiro的拦截器配置,新建ShiroConfig配置类,配置过滤器

/**
 * @Description springboot中的Shiro配置类
 * @Author 张小黑的猫
 * @data 2019-05-22 17:17
 */
@Configuration
public class ShiroConfig {
    
    /**
     * 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean webFilter(){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
        // Map<K,V> K指的是拦截的url V值的是该url是否拦截
        Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);

        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
        filterChainMap.put("/login","anon");
        filterChainMap.put("/**", "authc");

        //设置拦截请求后跳转的URL.
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }
}
  1. Shiro的用户认证
    先说下shiro认证的流程:
    创建SecurityManager安全管理器 > 主体Subject提交认证信息 > SecurityManager安全管理器认证 > SecurityManager调用Authenticator认证器认证 >Realm验证
    有几个概念:

(1)首先在ShiroConfig配置类中创建SecurityManager安全管理器

public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        return securityManager;
    }

这里值得注意的一点是SecurityManager导包的时候选org.apache.shiro.mgt.SecurityManager;
(2)SecurityManager安全管理器需要到realm中去验证认证信息,所以给SecurityManager设置Realm
Shiro的Realm分为IniRealmJdbcRealm以及自定义realm,我们这里使用自定义的realm实现业务逻辑。新建自定义Realm类CustomRealm继承AuthorizingRealm

public class CustomRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

(3)重写AuthorizingRealm中的认证方法doGetAuthenticationInfo

@Override
    /**
     * 认证
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.获取用户输入的账号
        String username = (String)token.getPrincipal();
        //2.通过username从数据库中查找到user实体
         User user = getUserByUserName(username);
        if(user == null){
            return null;
        }
        //3.通过SimpleAuthenticationInfo做身份处理
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        //4.用户账号状态验证等其他业务操作
        if(!user.getAvailable()){
            throw new AuthenticationException("该账号已经被禁用");
        }
        //5.返回身份处理对象
        return simpleAuthenticationInfo;
    }

上面的token凭证是来自主体Subject.login(token)提交时的token,主体的提交下面会说到。
这里还有个比较有意思的点:new SimpleAuthenticationInfo(user,user.getPassword(),getName());中的参数问题。第一个参数是从数据库中获取的User对象,第二个参数是数据库获取的密码,第三个参数是当前Realm的名称。其中第一个参数传username也可以,那么传usernameUser对象的区别是啥呢?Shiro为我们提供了获取当前用户信息的方法:

     * shiro获取当前用户
     * @return
     */
    private User currentUser(){
        User currentUser = (User) SecurityUtils.getSubject().getPrincipal();
        return  currentUser;
    }

有些业务场景需要获取当前用户的信息(用户的状态等等)进行业务操作,那么上面的区别就显而易见了。如果传的是username那么用户的其他信息就拿不到了。
(4) 将自定义Realm注入到SecurityManager安全管理器中

public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //将自定义的realm交给SecurityManager管理
        securityManager.setRealm(new CustomRealm());
        return securityManager;
    }

(5) Shiro配置类的过滤器中启用安全管理器,即shiroFilterFactoryBean中配置SecurityManager

//设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());

(6) 主体提交认证信息,即登录请求

 @PostMapping("/login")
    public String login(String username, String password, Model model){

        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        Subject currentUser = SecurityUtils.getSubject();

        try {
            //主体提交登录请求到SecurityManager
            currentUser.login(token);
        }catch (IncorrectCredentialsException ice){
            model.addAttribute("msg","密码不正确");
        }catch(UnknownAccountException uae){
            model.addAttribute("msg","账号不存在");
        }catch(AuthenticationException ae){
            model.addAttribute("msg","状态不正常");
        }
        if(currentUser.isAuthenticated()){
            System.out.println("认证成功");
            model.addAttribute("currentUser",currentUser());
            return "success";
        }else{
            token.clear();
            return "login";
        }
    }

到这基本认证的流程就已经通了。在这里贴一下项目结构和全部代码,供大家参考:


image.png

pom.xml:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- shiro相关依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

ShiroConfig.java:

/**
 * @Description springboot中的Shiro配置类
 * @Author 张小黑的猫
 * @data 2019-05-22 17:17
 */
@Configuration
public class ShiroConfig {

    /**
     * 配置Shiro核心 安全管理器 SecurityManager
     * SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
     */
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //将自定义的realm交给SecurityManager管理
        securityManager.setRealm(new CustomRealm());
        return securityManager;
    }


    /**
     * 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean webFilter(){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        //配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
        // Map<K,V> K指的是拦截的url V值的是该url是否拦截
        Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
        //配置退出过滤器logout,由shiro实现
        filterChainMap.put("/logout","logout");
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
        filterChainMap.put("/login","anon");
        filterChainMap.put("/**", "authc");

        //设置默认登录的URL.
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }
}

CustomRealm.java:

/**
 * @Description: shiro的自定义realm
 * Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
 * @Author 张小黑的猫
 * @data 2019-05-22 17:51
 */
public class CustomRealm extends AuthorizingRealm {
    @Override
    /**
     * 认证
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.获取用户输入的账号
        String username = (String)token.getPrincipal();
        //2.通过username从数据库中查找到user实体
         User user = getUserByUserName(username);
        if(user == null){
            return null;
        }
        //3.通过SimpleAuthenticationInfo做身份处理
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        //4.用户账号状态验证等其他业务操作
        if(!user.getAvailable()){
            throw new AuthenticationException("该账号已经被禁用");
        }
        //5.返回身份处理对象
        return simpleAuthenticationInfo;
    }

    @Override
    /**
     * 授权
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        return null;
    }

    /**
     * 模拟通过username从数据库中查找到user实体
     * @param username
     * @return
     */
    private User getUserByUserName(String username){
        List<User> users = getUsers();
        for(User user : users){
            if(user.getUsername().equals(username)){
                return user;
            }
        }
        return null;
    }

    /**
     * 模拟数据库数据
     * @return
     */
    private List<User> getUsers(){
        List<User> users = new ArrayList<>();
        users.add(new User("张小黑的猫","123qwe",true));
        users.add(new User("张小黑的狗","123qwe",false));
        return users;
    }

}

User.java

/**
 * @Description 用户
 * @Author 张小黑的猫
 * @data 2019-05-22 19:18
 */
public class User {
    private String username;
    private String password;
    private Boolean available;

    public User(String username, String password, Boolean available) {
        this.username = username;
        this.password = password;
        this.available = available;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Boolean getAvailable() {
        return available;
    }

    public void setAvailable(Boolean available) {
        this.available = available;
    }
}

LoginController.java:

/**
 * @Description 登录
 * @Author 张小黑的猫
 * @data 2019-05-22 18:17
 */
@Controller
public class LoginController {

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


    @PostMapping("/login")
    public String login(String username, String password, Model model){

        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        Subject currentUser = SecurityUtils.getSubject();

        try {
            //主体提交登录请求到SecurityManager
            currentUser.login(token);
        }catch (IncorrectCredentialsException ice){
            model.addAttribute("msg","密码不正确");
        }catch(UnknownAccountException uae){
            model.addAttribute("msg","账号不存在");
        }catch(AuthenticationException ae){
            model.addAttribute("msg","状态不正常");
        }
        if(currentUser.isAuthenticated()){
            System.out.println("认证成功");
            model.addAttribute("username",username);
            return "success";
        }else{
            token.clear();
            return "login";
        }
    }
}

login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form action="/login" method="post">
    <span th:text="${msg}" style="color: red"></span><br>
    用户名:<input type="text" name="username"><br>
    密&emsp;码:<input type="password" name="password"><br>
    <input type="submit" value="Login">
</form>
</body>
</html>

success.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>success</title>
</head>
<body>
<span th:text="'欢迎你,'+${username}"></span>
</body>
</html>

共同学习,欢迎指正修改~ 喵喵喵❤
下一篇文章:Springboot整合Shiro: 详细的权限管理

上一篇下一篇

猜你喜欢

热点阅读