springboot-shiro 使用

功能:解决web站点的登录,权限验证,授权等功能

优点:在不影响站点业务代码,可以权限的授权与验证横切到业务中

比较:shiro比Security 划分更细化

项目使用思考:

1:授权中用户的权限。变动不大,可以存在缓存中,不用每次都去数据库中查一次

2:ShiroFilterFactoryBean 中要验证的权限,由于只在程序初始化的时候执行。后期对角色或权限修改,还要重启项目。(是否要在更新或修改权限的时候,动态更新问题)

 

依赖包:

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.6.0</version>
        </dependency>
        <!--thymeleaf整合shiro-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

 

PS: 用户的授权信息,最好存入缓存中,不用每次都从缓存中取。本实例中未用缓存

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleservice;

    @Autowired
    private PermissionService permissionService;

    /**
     * 授权(为当前登录成功的用户授予权限和分配角色)
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 1、获取用户
        User user = (User) principals.getPrimaryPrincipal();
        if (user == null) {
            log.warn("授权失败,用户信息异常,用户{}", this.getClass().getName());
            return authorizationInfo;
        }

        // 2、根据用户名去数据库中查询用户信息(查询该认证用户下角色/权限信息)
        List<Role> roleList = roleservice.getRoleListByUserName(user.getUserName());
        List<Permission> permissionList=null;
        if(roleList.size()>0){
            for(Role role:roleList){
                //添加角色
                authorizationInfo.addRole(role.getRole());//添加角色名称(要唯一)
                permissionList=permissionService.getPermissionListByRoleId(role.getRoleId());
                for(Permission permission : permissionList){
                    //添加权限
                    authorizationInfo.addStringPermission(permission.getPermission());//添加权限(user:add)
                }
            }
        }
        log.info("====doGetAuthorizationInfo注册完成====");
        return authorizationInfo;
    }

    /**
     * 认证、登录(用来验证当前登录的用户,获取认证信息)
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.把AuthenticationToken类型的token转换为UsernamePasswordToken(封装了从前端传递的用户名和密码(用户输入))
        UsernamePasswordToken userToken=(UsernamePasswordToken)token;

        //2.通过UsernamePasswordToken获取username
        String username = userToken.getUsername();
        if(!StringUtils.hasText(username)){
            return null;
        }
        // 3.通过username查询数据库,且判断数据库中的用户状态
        User user= userService.findByUserName(username);
        if(user==null){
            throw new UnknownAccountException("该用户不存在");
        }
        if(user.getState()==0){
            throw new AuthenticationException("创建未认证");
        }
        if(user.getState()==2){
            throw new LockedAccountException("该用户被锁定");
        }

        // 4.传入用户名和密码进行身份认证,并返回认证信息(盐值可以不传【加盐后相同密码加密后不一致】)
        // 认证的第一个参数 可以是Username也可以是User实体类对象
        // 如果传的参数为username,那么在授权阶段,使用principals.getPrimaryPrincipal();获取到的就是Username
        // 如果传的参数为user对象,那么在授权阶段,获取到的就是user对象
        AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(
                user,
                user.getPassword(),
                ByteSource.Util.bytes("1"),
                this.getClass().getName()
        );
        log.info("====doGetAuthenticationInfo注册完成====");
        return authcInfo;
    }

 
}

 

PS:filterChainMap.put("/user/**", "authc"); //过滤链定义,从上向下顺序执行,一般将 /**放在最为下边(否则可能导致 授权不会执行)

@Configuration
@Slf4j
public class ShiroConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;


    /**
     * 注入自定义的 Realm [将自己的验证方式加入容器]
     *
     * @return
     */
    @Bean
    public MyRealm myAuthRealm() {
        MyRealm myRealm = new MyRealm();//配置自定义密码比较器
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myRealm;
    }

    /**
     * 注入安全管理器(配置 SecurityManager 时,需要将上面自定义 Realm 添加进来,这样 Shiro 才可访问该 Realm)
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        // 将自定义 Realm 加进来
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
        log.info("====securityManager注册完成====");
        //PS:DefaultWebSecurityManager无法转SecurityManager,需要手动导入此包(import org.apache.shiro.mgt.SecurityManager;)

        return securityManager;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, PermissionService permissionService) {
        // 定义 shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/user/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/user/index");
        // 未授权界面(认证不通过跳转);
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/403");

        // 拦截器(LinkedHashMap 是有序的,进行顺序拦截器配置)
        Map<String, String> filterChainMap = new LinkedHashMap<>();
        // 配置不会被拦截的链接 顺序判断【anon表示放行】
        filterChainMap.put("/static/**", "anon");
        filterChainMap.put("/public", "anon");
        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainMap.put("/user/logout", "logout");

        //添加要检查的权限(如果改动在数据库中添加或删除权限,不重启程序,权限是不会生效的。需要动态配置)
        List<Permission> permissionList = permissionService.getAllPermissionList();
        for (Permission permission : permissionList) {
            filterChainMap.put(permission.getUrl(), "perms[" + permission.getPermission() + "]");
        }
        //filterChainMap.put("/user/userAdd", "perms[user:add]");
        //filterChainMap.put("/user/userDel", "perms[user:del]");


        // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
        // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainMap.put("/user/**", "authc");
        // 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);

        log.info("====shiroFilterFactoryBean注册完成====");

        return shiroFilterFactoryBean;
    }/**
     * 开启shiro 注解模式
     * 可以在controller中的方法前加上注解
     * 如 @RequiresPermissions("userInfo:add")
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

/**
     * 凭证匹配器(密码校验交给Shiro的SimpleAuthenticationInfo进行处理)
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        //使用MD5加密
//        Md5Hash md5 = new Md5Hash("admin", "1", 2);
//        log.info("====求得密码:"+md5+"====");

        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 加密方式
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 加密两次
        hashedCredentialsMatcher.setHashIterations(2);
        // 是否存储为16进制
        //hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

}

 

PS:注意权限更新后,动态更新权限(ShiroFilterFactoryBean 只在初始化的时候执行,新增或修角色改权限时,需要动态更新)

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

//获取数据空的权限 @Autowired ShiroService shiroService;
/** * 修改数据库中的权限后,动态更新权限 * @return */ @RequestMapping("/updatePermission") public String updatePermission(){ shiroService.updatePermission(); return "redirect:/user/userList"; } @RequestMapping("/index2") public String index2(){ return "/shiro/index"; } @RequestMapping("/index") public String index(){ return "/shiro/index"; } @RequestMapping("/403") public String unauthorized(){ return "/shiro/unauthorized"; } @RequestMapping("/logout") public String logout(){ // 获取 subject 认证主体 Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/user/login"; } @RequestMapping("/login") public String login(){ return "/shiro/login"; } @RequestMapping(value = "/login",method = RequestMethod.POST) public String login(User user, HttpServletRequest request, RedirectAttributes redirectAttributes, Model model){ //RedirectAttributes : 用于重定向带参数 if (user.getUserName()== null || user.getUserName().isEmpty()) { redirectAttributes.addFlashAttribute("message", "用戶名不能爲空"); return "redirect:/user/login"; } // 获取 subject 认证主体 Subject currentUser = SecurityUtils.getSubject(); //currentUser.isAuthenticated()当前用户是否被认证 //如果当前用户被认证了,说明用户还是处于登录状态 if(!currentUser.isAuthenticated()) { // 把需要登录的用户名和密码存入shiro提供的令牌中 UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(),user.getPassword()); try { // subject调用login方法来进行匹配用户是否可以登录成功 // login方法的参数需要接收shiro的UsernamePasswordToken类型 currentUser.login(token); //Session session = currentUser.getSession(); // 设置会话session //session.setAttribute("userName", user.getUserName()); //登录成功写入Session等其他操作 //request.getSession().setAttribute("user",user); return "/shiro/index"; } catch(UnknownAccountException uae){ log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,未知账户"); redirectAttributes.addFlashAttribute("message", "未知账户"); }catch(IncorrectCredentialsException ice){ log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,错误的凭证"); redirectAttributes.addFlashAttribute("message", "密码不正确1"); }catch(LockedAccountException lae){ log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,账户已锁定"); redirectAttributes.addFlashAttribute("message", "账户已锁定"); }catch(AuthenticationException ae){ //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printStackTrace(); redirectAttributes.addFlashAttribute("message", "用户名或密码不正确"); } return "redirect:/user/login"; } //Tis:return 中 redirect:后面是控制器的路由地址,不是页面地址 return "redirect:/user/index"; //return "/shiro/index"; } }
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

<h3 shiro:principal>登录人</h3> <shiro:hasPermission name="user:add"> <a th:href="@{/add}" >添加</a> </shiro:hasPermission> <shiro:hasPermission name="user:del"> <a th:href="@{/del}" >删除</a> </shiro:hasPermission>

 

posted @ 2020-11-23 17:56  人间有妖气  阅读(195)  评论(0编辑  收藏  举报