SpringBootSpringBoot权限安全

SpringBoot + Shiro (二)身份校验和角色设置

2017-07-05  本文已影响915人  忧郁的小码仔

最终demo

开始之前,我们先造一些数据:

INSERT INTO test.sys_permission VALUES ('1', 1, '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO test.sys_permission VALUES ('2', 1, '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO test.sys_permission VALUES ('3', 1, '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');

INSERT INTO test.sys_role VALUES ('1', 1, '管理员', 'admin');
INSERT INTO test.sys_role VALUES ('2', 1, 'VIP会员', 'vip');

INSERT INTO test.user_info (uid,username,name,password,salt,state) VALUES ('1', 'admin','管理员' , 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);

INSERT INTO test.sys_role_permission VALUES ('1', '1');
INSERT INTO test.sys_role_permission VALUES ('1', '2');

INSERT INTO test.sys_user_role VALUES ('1', '1');

数据有了,下面开始Shrio的配置学习。
开始之前先添加Shiro依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.2</version>
        </dependency>

1.Realms

Realm是一个Dao,通过它来验证用户身份和权限。这里Shiro不做权限的管理工作,需要我们自己管理用户权限,只需要从我们的数据源中把用户和用户的角色权限信息取出来交给Shiro即可。
config包下再建一个包Shiro,然后在Shiro包下建一个MyShiroRealm类,继承AuthorizingRealm抽象类。

public class MyShiroRealm extends AuthorizingRealm {

    @Resource
    private UserInfoService userInfoService;



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("开始身份验证");
        String username = (String) token.getPrincipal(); //获取用户名,默认和login.html中的username对应。
        UserInfo userInfo = userInfoService.findByUsername(username);

        if (userInfo == null) {
            //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
            return null;
        }

  //验证通过返回一个封装了用户信息的AuthenticationInfo实例即可。
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //用户信息
                userInfo.getPassword(), //密码
                getName() //realm name
        );
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userInfo.getCredentialsSalt())); //设置盐

        return authenticationInfo;
    }

//当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        System.out.println("开始权限配置");

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();

        for (SysRole role: userInfo.getRoleList()) {
            authorizationInfo.addRole(role.getRole());
            for (SysPermission p: role.getPermissions()) {
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }

        return authorizationInfo;
    }
}

重载以上两个方法来配置用户身份验证和权限验证。

别忘了在Service包下新建个UserInfoService和它的实现类:

public interface UserInfoService {

    public UserInfo findByUsername(String username);
}
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserInfoRepository userInfoRepository;

    @Override
    public UserInfo findByUsername(String username) {

        System.out.println("UserInfoServiceImpl.findByUsername");
        return userInfoRepository.findByUsername(username);
    }
}

dao层下新建一个UserInfo的repository

public interface UserInfoRepository extends CrudRepository<UserInfo, Long> {

    public UserInfo findByUsername(String username);

    public UserInfo save(UserInfo userInfo);
}

2.接下来配置Shiro的关键部分

这里要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。

@Configuration
public class ShiroConfiguration {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        //authc表示需要验证身份才能访问,还有一些比如anon表示不需要验证身份就能访问等。


        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/index");
//        shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //这里设置403并不会起作用,参考http://www.jianshu.com/p/e03f5b54838c

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    //SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)  
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm()); //将Realm注入到SecurityManager中。
        return securityManager;
    }

    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); //设置解密规则
        return myShiroRealm;
    }

   //因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }
}

关于为什么设置filterChainDefinitionMap.put("/favicon.ico", "anon");,请参考Shiro登录后下载favicon.ico问题

3.修改我们的HomeController中的/login请求

    // 这里如果不写method参数的话,默认支持所有请求,如果想缩小请求范围,还是要添加method来支持get, post等等某个请求。
    @RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {

        System.out.println("HomeController.login");

        // 登录失败从request中获取shiro处理的异常信息。
        // shiroLoginFailure:就是shiro异常类的全类名.
        Object exception = request.getAttribute("shiroLoginFailure");
        String msg = "";
        if (exception != null) {
            if (UnknownAccountException.class.isInstance(exception)) {
                System.out.println("账户不存在");
                msg = "账户不存在或密码不正确";
            } else if (IncorrectCredentialsException.class.isInstance(exception)) {
                System.out.println("密码不正确");
                msg = "账户不存在或密码不正确";
            } else {
                System.out.println("其他异常");
                msg = "其他异常";
            }
        }

        map.put("msg", msg);
        // 此方法不处理登录成功,由shiro进行处理.
        return "login";
    }

这里@RequestMapping之所以没加method是因为如果用户没登录,Shiro会调用get方法请求/login,而后面我们在login页面会用post请求发送form表单,所以这里就没设置method(默认支持所有请求)。

好,启动项目。这时候我们再访问http://localhost:8080/index会跳转到登录页面,因为我们设置了filterChainDefinitionMap.put("/**", "authc");,所以要先验证身份才能访问。同时因为设置了shiroFilterFactoryBean.setLoginUrl("/login");,所以会跳转到登录页面。这时候在登录页面输入正确的用户名密码就可以登录了,登录成功会自动跳转到index页面。

这一节先到此为止。

到此的项目结构:

项目结构

SpringBoot + Shiro (一)基础工程搭建
SpringBoot + Shiro (二)身份校验和角色设置
SpringBoot + Shiro (三)权限
SpringBoot + Shiro (四)缓存&记住密码
SpringBoot + Shiro (五)验证码

最后,感谢几位作者的文章解惑:
springboot整合shiro-登录认证和权限管理
Spring Boot Shiro权限管理【从零开始学Spring Boot】
Spring boot 中使用Shiro

上一篇下一篇

猜你喜欢

热点阅读