Spring Boot 使用Shiro进行安全管理

image.png

本文使用SpringBoot2.0+ MyBatis + Shiro 实现一个简单的安全管理。对集成MyBatis可以参考文章 Spring Boot 整合MyBatis

添加依赖

 
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.2
        

        
        
            mysql
            mysql-connector-java
            runtime
        

        
        
            org.projectlombok
            lombok
            true
        

        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            org.apache.shiro
            shiro-spring
            1.4.0
        
    

创建实体类和数据访问服务

创建3个实体类:UserRolePermission

@Data
public class User implements Serializable {
    private static final long serialVersionUID = -8315794285126194641L;
    private Integer id;
    private String userName;
    private String password;
    private String status;
}
@Data
public class Role implements Serializable {
    private static final long serialVersionUID = -7713029747061173171L;

    private Integer id;
    private String name;
    private String memo;
}
@Data
public class Permission implements Serializable {
    private static final long serialVersionUID = -316747523328447976L;

    private Integer id;
    private String url;
    private String name;
}

数据访问层

@Component
public interface UserMapper {

    /**
     * 根据用户名查询对应的用户信息
     *
     * @param userName
     * @return
     */
    User findByUserName(String userName);
}

@Component
public interface UserPermissionMapper {

    /**
     * 根据用户名查询用户所拥有的操作权限集合
     *
     * @param username
     * @return
     */
    List findPermsByUsername(String username);
}

@Component
public interface UserRoleMapper {
    /**
     * 查询用户所属的角色列表
     *
     * @param username
     * @return
     */
    List findRolesByUsername(String username);
}

然后创建一个UserService服务,用来读取用户,角色等信息

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private UserPermissionMapper userPermissionMapper;

    /**
     * 根据用户名查找对应的用户信息
     *
     * @param userName
     * @return
     */
    public User findByUserName(String userName) {
        return userMapper.findByUserName(userName);
    }

    /**
     * 根据用户名查找对应的角色集合
     *
     * @param username
     * @return
     */
    public List findRolesByUsername(String username) {
        return userRoleMapper.findRolesByUsername(username);
    }

    /**
     * 根据用户名查找对应的操作权限集合
     *
     * @param username
     * @return
     */
    public List findPermsByUsername(String username) {
        return userPermissionMapper.findPermsByUsername(username);
    }

}

访问sql语句的xml文件

UserMapper.xml





    
        
        
        
        
    

    


UserRoleMapper.xml





    
        
        
        
    

    


UserPermission.xml





    
        
        
        
    

    


配置Shiro

SSM架构下都是配置在xml文件中,SpringBoot通过代码来进行配置
新建一个ShiroConfig的类,代码如下:

  @Configuration
public class ShiroConfig {

    /**
     * 配置过滤器链,过滤器配置要求是有顺序的,并且是短路优先原则,即前面的匹配到之后,就不再执行后面的过滤器了
     * 1)注入SecurityManager
     * 2)配置过滤器规则
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        LinkedHashMap filterChainMap = new LinkedHashMap<>(12);
        // 静态资源不予拦截
        filterChainMap.put("/css/**", "anon");
        filterChainMap.put("/js/**", "anon");
        filterChainMap.put("/fonts/**", "anon");
        filterChainMap.put("/img/**", "anon");
        // 退出
        filterChainMap.put("/logout", "anon");
        // 退出
        filterChainMap.put("/", "anon");
        // 除了以上url外,其他的都需要认证通过才可以访问,否则转向login
        filterChainMap.put("/**", "authc");

        // TODO 未能拦截到403
//        filterChainMap.put("/user/delete", "perms[user:delete]");

        // 登录url
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 未授权url
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        // 登录成功后的跳转
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 设置拦截规则
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }


    // ----------------------------------------开启注解 BEGIN--------------------------------------------


    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 启用角色,权限注解,例如:@RequiresPermissions,@RequiresRoles
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    //----------------------------------------开启注解 END--------------------------------------------

    /**
     * 配置SecurityManager,核心
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }

    /**
     * 自定义Realm,获取用户的安全数据。机制就是把前端传递的用户名和密码和数据库中的信息比对,验证登录是否合法,并且得到登录用户的角色信息,权限信息
     * 1)登录验证
     * 2)角色,权限校验
     *
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm() {
        return new ShiroRealm();
    }

    /**
     * Shiro生命周期处理器
     *
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


}

然后再新建一个自定义的Realm,这里新建一个ShiroRealm的类,代码如下:

public class ShiroRealm extends AuthorizingRealm {

    /**
     * 一个服务,可以从数据库查询用户信息,包含用户名和密码,查询用户所属的角色,用户所拥有的权限
     */
    @Autowired
    private UserService userService;


    /**
     * 验证当前登录用户的角色信息,权限信息
     * 把当前登录客户的角色信息集合,可操作权限集合从数据库中取出来,
     * 然后保存到SimpleAuthorizationInfo对象中,并返回给Shiro,这样Shiro中就存储了当前用户的角色和权限信息了
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        String username = user.getUserName();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        System.out.println("角色校验,获取权限" + username);

        // 获取用户角色集
        List roleList = userService.findRolesByUsername(username);
        Set roleSet = new HashSet<>(roleList.size());
        for (Role role : roleList) {
            roleSet.add(role.getName());
        }
        info.setRoles(roleSet);

        // 获取用户权限集
        List permissionList = userService.findPermsByUsername(username);
        Set permissionSet = new HashSet<>(permissionList.size());
        for (Permission permission : permissionList) {
            permissionSet.add(permission.getName());
        }
        info.setStringPermissions(permissionSet);
        return info;
    }


    /**
     * 登录验证
     * 根据客户端传递的登录信息,然后从数据库中取出对应的用户录,进行比对,看是否匹配
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        /**
         * 从AuthenticationToken中获取到前端传递的用户名和密码字段信息
         */
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());

        System.out.println("认证用户:" + username + "---" + password);

        /**
         * 从数据库中查询对应的用户记录
         */
        User user = this.userService.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;
    }
}

开发界面

登录功能

新建一个LoginController

@Controller
public class LoginController {

    /**
     * 查询用户信息服务
     */
    @Autowired
    private UserService userService;

    /**
     * 返回登录页面
     *
     * @return
     */
    @GetMapping("/login")
    public String login() {
        return "login";
    }

    /**
     * 测试方法,得到一个用户数据,返回json格式
     *
     * @return
     */
    @GetMapping("/user")
    @ResponseBody
    public User gerUser() {
        return userService.findByUserName("mrbird");
    }

    /**
     * 跳转首页
     *
     * @param model
     * @return
     */
    @GetMapping("/index")
    public String index(Model model) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("user", user);
        return "index";
    }

    /**
     * 默认情况下跳转首页
     *
     * @return
     */
    @GetMapping("/")
    public String redirectIndex() {
        return "redirect:/index";
    }

    /**
     * 登录方法,post形式
     *
     * @param username
     * @param password
     * @return
     */
    @PostMapping("/login")
    @ResponseBody
    public HttpResult login(String username, String password) {
        String encPassword = MD5Utils.encrypt(username, password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, encPassword);
        Subject subject = SecurityUtils.getSubject();

        try {
            subject.login(token);
            return HttpResult.ok();
        } catch (UnknownAccountException e) {
            return HttpResult.error(e.getMessage());
        } catch (IncorrectCredentialsException e) {
            return HttpResult.error(e.getMessage());
        } catch (LockedAccountException e) {
            return HttpResult.error(e.getMessage());
        } catch (AuthenticationException e) {
            return HttpResult.error("认证失败");
        }
    }

    /**
     * 没有权限跳转的界面
     *
     * @return
     */
    @GetMapping("/403")
    public String forbid() {
        return "403";
    }

    /**
     * 用户注销登录
     *
     * @return
     */
    @GetMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "redirect:/index";
    }
}

然后在resources>templates文件夹下新建一个login.html和index.html

运行之后,地址栏输入http://localhost:8080 进行访问时,都会重新定位到登录页面,登录成功之后,会跳转到index页面。

image.png

Shiro为我们提供了一些和权限相关的注解,如下所示:

// 表示当前Subject已经通过login进行了身份验证;即Subject.isAuthenticated()返回true。
@RequiresAuthentication  
 
// 表示当前Subject已经身份验证或者通过记住我登录的。
@RequiresUser  
// 表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresGuest  
// 表示当前Subject需要角色admin和user。  
@RequiresRoles(value={"admin", "user"}, logical= Logical.AND)  
// 表示当前Subject需要权限user:a或user:b。
@RequiresPermissions (value={"user:a", "user:b"}, logical= Logical.OR)

添加一个UserController

Controller
@RequestMapping("/user")
public class UserController {

    /**
     * 需要用户有list操作权限
     *
     * @param model
     * @return
     */
    @RequiresPermissions("user:list")
    @RequestMapping("list")
    public String userList(Model model) {
        model.addAttribute("value", "获取用户信息");
        return "user";
    }

    /**
     * 需要用户有add操作权限
     *
     * @param model
     * @return
     */
    @RequiresPermissions("user:add")
    @RequestMapping("add")
    public String userAdd(Model model) {
        model.addAttribute("value", "新增用户");
        return "user";
    }

    /**
     * 需要用户有delete操作权限
     *
     * @param model
     * @return
     */
    @RequiresPermissions("user:delete")
    @RequestMapping("delete")
    public String userDelete(Model model) {
        model.addAttribute("value", "删除用户");
        return "user";
    }
}

代码示例

https://github.com/xiaozhaowen/spring-boot-in-action/tree/master/springboot-shiro

你可能感兴趣的:(Spring Boot 使用Shiro进行安全管理)