springboot+shiro整合

2019-03-27  本文已影响0人  _麻辣香锅不要辣

springboot+shiro

最近在看shiro和jwt,遇到了好多坑,现在总结一下

不了解shiro的话,可以先看下这个:shiro基础教程

以下内容参考(写的很简洁明了):教你 Shiro 整合 SpringBoot,避开各种坑

导包


        <dependency

            <groupId>org.apache.shiro</groupId>

            <artifactId>shiro-spring</artifactId>

            <version>1.3.2</version>

        </dependency>

springboot整合shiro

shiro配置类


@Configuration

public class ShiroConfig {

    @Bean

    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {

       // RestShiroFilterFactoryBean shiroFilterFactoryBean = new RestShiroFilterFactoryBean();

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ()
        // 必须设置 SecurityManager

        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射

        shiroFilterFactoryBean.setLoginUrl("/notLogin");

        // 设置无权限时跳转的 url;

        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");

        // 设置拦截器

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        filterChainDefinitionMap.put("/logout","anon");

        filterChainDefinitionMap.put("/login","anon");

        filterChainDefinitionMap.put("/user/**","roles[user]");

        //其余接口一律拦截

        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截

        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        System.out.println("Shiro拦截器工厂类注入成功");

        return shiroFilterFactoryBean;

    }

    /**

    * 注入 securityManager

    */

    @Bean

    public SecurityManager securityManager() {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 设置realm.

        securityManager.setRealm();

        //securityManager.setRealms(Arrays.asList(customRealm(hashedCredentialsMatcher()),jwtRealm()));

        return securityManager;

    }

    /**

    * 自定义身份认证 realm;

    * <p>

    * 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,

    * 否则会影响 CustomRealm类 中其他类的依赖注入

    */

    @Bean

    public CustomRealm customRealm() {

        CustomRealm customRealm=new CustomRealm();

        return  customRealm;

    }

    /**

    * 禁用session, 不保存用户登录状态。保证每次请求都重新认证。

    * 需要注意的是,如果用户代码里调用Subject.getSession()还是可以用session,如果要完全禁用,要配合下面的noSessionCreation的Filter来实现

    */

    @Bean

    protected SessionStorageEvaluator sessionStorageEvaluator(){

        DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();

        sessionStorageEvaluator.setSessionStorageEnabled(false);

        return sessionStorageEvaluator;

    }

}

注意:里面的 SecurityManager 类导入的应该是 import org.apache.shiro.mgt.SecurityManager; 但是,如果你是复制代码过来的话,会默认导入 java.lang.SecurityManager 这里也稍稍有点坑,其他的类的话,也是都属于 shiro 包里面的类

shirFilter 方法中主要是设置了一些重要的跳转 url,比如未登陆时,无权限时的跳转;以及设置了各类 url 的权限拦截,比如 /user 开始的 url 需要 user 权限,/admin 开始的 url 需要 admin 权限等

(这里的跳转会涉及到跨域的问题,因为rediret重定向在跨域中会有问题)

注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url )

自定义realm类


public class CustomRealm extends AuthorizingRealm {

    @Autowired

    private UserMapper userMapper;

    /**

    *  当token类型是UsernamePasswordToken时才调用这个realm(在之后整合jwt的时候还有一个jwttoken,所以这里要加这个)

    */

    @Override

    public boolean supports(AuthenticationToken token) {

        return token instanceof UsernamePasswordToken;

    }

    /**

    * 身份认证

    * Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。

    * @param authenticationToken 用户身份信息 token

    * @return 返回封装了用户信息的 AuthenticationInfo 实例

    */

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        // 从数据库获取对应用户名密码的用户

            User user = userMapper.selectbyUsername(token.getUsername());

            String password="";

            if(user!=null)

            {password=user.getPassword();}

        return new SimpleAuthenticationInfo(token.getPrincipal(),

        password,

          getName());

    }

    /**

    * 权限认证

    * @param principalCollection

    * @return

    */

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        String username = (String) SecurityUtils.getSubject().getPrincipal();

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //获得该用户角色

        String role=userMapper.selectbyUsername(username).getRole();

        //String role = userMapper.getRole(username);

        Set<String> set = new HashSet<>();

        //需要将 role 封装到 Set 作为 info.setRoles() 的参数

        set.add(role);

        //设置该用户拥有的角色

        info.setRoles(set);

        return info;

    }

}

重写的两个方法分别是实现身份认证以及权限认证,shiro 中有个作登陆操作的 Subject.login() 方法,当我们把封装了用户名,密码的 token 作为参数传入,便会跑进这两个方法里面(不一定两个方法都会进入)

其中 doGetAuthorizationInfo 方法只有在需要权限认证时才会进去,比如前面配置类中配置了 filterChainDefinitionMap.put("/user/**", "roles[user]"); 的管理员角色,这时进入 /user 时就会进入 doGetAuthorizationInfo 方法来检查权限;而 doGetAuthenticationInfo 方法则是需要身份认证时(比如前面的 Subject.login() 方法)才会进入

再说下 UsernamePasswordToken 类,我们可以从该对象拿到登陆时的用户名和密码(登陆时会使用 new UsernamePasswordToken(username, password);)

loginController


@RequestMapping(value = "/login")

    public boolean login(@RequestBody User user ) {

    String username=user.getUsername();

    String password=user.getPassword();

        // 从SecurityUtils里边创建一个 subject

        Subject subject = SecurityUtils.getSubject();

        // 在认证提交前准备 token(令牌)

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        // 执行认证登陆

        //在登录时会调用CustomRealm中的doGetAuthenticationInfo方法,返回一个SimpleAuthenticationInfo

        //这个是在内部根据usernamepasswordtoken判断用户名密码是否正确,如果正确的话不会抛出异常,否则抛出

        //异常,在loginController中将异常捕获,并返回false

        try {

            subject.login(token);

            return true;

        }catch (Exception e)

        {

            return false;

        }

    }

上一篇下一篇

猜你喜欢

热点阅读