spring boot 集成shiro(用户授权和权限控制)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaogc_a/article/details/82528505

(1) pom.xml中添加Shiro依赖

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

(2) 注入Shiro Factory和SecurityManager

Shiro几个核心的类,第一就是ShiroFilterFactory,第二就是SecurityManager,那么最简单的配置就是注入这两个类就ok了

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;

@Component
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器.
        Map <String, String> filterChainDefinitionMap = new LinkedHashMap <String, String>();
//anon:所有url都都可以匿名访问;
//authc: 需要认证才能进行访问;
//user:配置记住我或认证通过可以访问;
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/authenRecharge/**", "anon");
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/interface/**", "anon");
        filterChainDefinitionMap.put("/manage/**", "anon");
        filterChainDefinitionMap.put("/wicket/**", "anon");
        filterChainDefinitionMap.put("/dbwizard/**", "anon");
        filterChainDefinitionMap.put("/mallbook/login", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/wicket/resource/**", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/mallbook/MallHouse");

        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
//这个很重要,将我们自定义的Realm注入到SecurityManager中
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    /**
     * 凭证匹配器
     *应为我们身份认证用的密码是加密的,所以需要一个加密算法 ,要是使用明文就不用了
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码; )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }
//身份认证realm;
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new         
             AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

(3) 身份认证,权限控制

认证实现

Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。

该方法主要执行以下操作:

1、检查提交的进行认证的令牌信息

2、根据令牌信息从数据源(通常为数据库)中获取用户信息

3、对用户信息进行匹配验证。

4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。

5、验证失败则抛出AuthenticationException异常信息。

而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。

shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm。

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class MallBookShiroRealm extends AuthorizingRealm {
    @Autowired
    private UpmsUserService userService;

    private final static Logger logger = LoggerFactory.getLogger(MallBookShiroRealm.class);

    /**
     * 权限认证,为当前登录的Subject授予角色和权限
     * <p>
     * 本例中该方法的调用时机为需授权资源被访问时
     * 并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * 如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("权限配置-->EpayShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
        for (RolesInfo role : userInfo.getRoles()) {
            authorizationInfo.addRole(role.getName());
            for (UpmsPermission p : role.getPermissions()) {
                if (null != p.getPermissionValue() && p.getPermissionValue().length() > 0) {
                    authorizationInfo.addStringPermission(p.getPermissionValue());
                }
            }
        }
        return authorizationInfo;
    }


    /**
     * 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        logger.info("正在验证身份...");
        // 获取用户的输入的账号.
        //将token转换成UsernamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = (String) token.getPrincipal();
        System.out.println(token.getCredentials());

        // 通过username从数据库中查找 User对象,如果找到,没找到.
        // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userService.findByUsername(username);
        System.out.println("----->>userInfo=" + userInfo);
        if (null == userInfo) {
            return null;
        }
        // 得到盐值加密后的密码:只用于方便数据库测试,后期不会用到。
        return new SimpleAuthenticationInfo(
                userInfo,
                userInfo.getPassword(),
                ByteSource.Util.bytes(userInfo.getSalt()),
                getName()
        );
    }
}

继承AuthorizingRealm主要需要实现两个方法:
doGetAuthenticationInfo();
doGetAuthorizationInfo();
其中doGetAuthenticationInfo主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
SimpleAuthenticationInfo oauthenticationInfo =
  return new SimpleAuthenticationInfo(
                userInfo,//用户名
                userInfo.getPassword(),//密码
                ByteSource.Util.bytes(userInfo.getSalt()),//加密的盐
                getName()//realm name
        );


doGetAuthorizationInfo()是权限控制,当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
主要的类:SimpleAuthorizationInfo 
  for (RolesInfo role : userInfo.getRoles()) {
            authorizationInfo.addRole(role.getName());//添加角色
            for (UpmsPermission p : role.getPermissions()) {
                if (null != p.getPermissionValue() && p.getPermissionValue().length() > 0) {
                    authorizationInfo.addStringPermission(p.getPermissionValue());//添加菜单
                }
            }
        }

到这里的话身份认证权限控制基本是完成了,最后我们在编写一个登录的时候,登录的处理

import java.util.ArrayList;
import java.util.List;
@RestController
public class LoginController{
    Logger log= LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private UpmsUserService userService;

    @RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)
    public Object login(String username, String password) {
        String passwordNew= RSAUtils.decrypt(password, RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
        UsernamePasswordToken token = new UsernamePasswordToken(username, passwordNew);
        token.setHost("localhost");
        Subject currentUser = SecurityUtils.getSubject();

        UpmsResultConstant messageCode = UpmsResultConstant.LOGIN_SUCCESS;
        if (null == userService.getUserInfo(token.getUsername())) {
            return new UpmsResult(UpmsResultConstant.FAILED, UpmsResultConstant.FAILED.code);
        }
        try {
            currentUser.login(token);
        } catch (UnknownAccountException ex) {
            messageCode = UpmsResultConstant.LOGIN_ACCOUNT_ERROR;
            ex.printStackTrace();
        } catch (IncorrectCredentialsException ex) {
            messageCode = UpmsResultConstant.LOGIN_PASSWORD_ERROR;
            ex.printStackTrace();
        } catch (LockedAccountException ex) {
            messageCode = UpmsResultConstant.LOGIN_ACCOUNT_LOCKED;
            ex.printStackTrace();
        } catch (ExcessiveAttemptsException ex) {
            messageCode = UpmsResultConstant.LOGIN_EXCESSIVE_ERROR;
            ex.printStackTrace();
        } catch (AuthenticationException ex) {
            messageCode = UpmsResultConstant.LOGIN_AUTHTICATION_FAIL;
            ex.printStackTrace();
        }

        if (currentUser.isAuthenticated()) {
            LoginInfo loginInfo = new LoginInfo();
            UserInfo userInfo = (UserInfo) currentUser.getPrincipals().getPrimaryPrincipal();
            loginInfo.setAvatar(userInfo.getAvatar());
            loginInfo.setIntroduction(userInfo.getRole());
            loginInfo.setName(userInfo.getUserName());
            loginInfo.setToken(currentUser.getSession().getId().toString());
            if (null != userInfo.getRoles() && userInfo.getRoles().size() > 0) {
                List<String> roles = new ArrayList<>();
                for (RolesInfo rolesInfo : userInfo.getRoles()) {
                    roles.add(rolesInfo.getName());
                }
                loginInfo.setRole(roles);
            } else {
                return new UpmsResult(UpmsResultConstant.FAILED, UpmsResultConstant.FAILED.code);
            }

            return new UpmsResult(UpmsResultConstant.SUCCESS, loginInfo);
        } else {
            return new UpmsResult(messageCode, 0);
        }
    }

 
    @RequestMapping(value = "/getUserInfo")
    public Object getUserInfo() {
        UserInfo userInfo = UserUtils.getUser();
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setAvatar(userInfo.getAvatar());
        loginInfo.setIntroduction(userInfo.getRole());
        loginInfo.setName(userInfo.getRealName());
        loginInfo.setToken(userInfo.getToken());
        List<String> roles = new ArrayList<String>();
        List<String> pms = new ArrayList<String>();
        for (RolesInfo rolesInfo : userInfo.getRoles()) {
 
            roles.add(rolesInfo.getTitle());
            for (UpmsPermission permission : rolesInfo.getPermissions()) {
                pms.add(permission.getPermissionValue());
            }
        }
        loginInfo.setPms(pms);
        loginInfo.setRole(roles);
        loginInfo.setMchId(userInfo.getUserId().toString());
        return new UpmsResult(UpmsResultConstant.SUCCESS, loginInfo);
    }
}

这个时候访问http://localhost:9527/#/login就可以跳转到index页面了

此时身份认证是好了,但是权限控制好像还没有作用

还需要两部分:

第一就是开启shiro aop注解支持

在ShiroConfig需要开启shiro aop注解支持.

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {

AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();

advisor.setSecurityManager(securityManager);

return advisor;

}

第二就是在controller方法中加入相应的注解:

@RequiresPermissions("sys_user_add")//权限管理

@RestController
@RequestMapping(value = "/manage/user")
public class UserController {
    @Autowired
    private UpmsUserService userService;
    private static Logger log = LoggerFactory.getLogger(UserController.class);
    @Autowired
    IdWorker idWorker;

   

    @RequiresPermissions("user_manager")
    @RequestMapping(value = "/list", method = RequestMethod.POST)
    @ResponseBody
    public Object list(UpmsUser user) {
        return new UpmsResult(
                UpmsResultConstant.SUCCESS
                , userService.listUser(user)
        );
    }
    
    @RequiresPermissions("sys_user_edit")
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public Object update(UpmsUser upmsUser) {

        log.info("-------------------------------");
        // 逻辑验证
        ComplexResult result = FluentValidator.checkAll()
                .doValidate()
                .result(ResultCollectors.toComplex());
        if (!result.isSuccess()) {
            return new UpmsResult(UpmsResultConstant.INVALID_LENGTH, result.getErrors());
        }
        String passwordNew = "";
        if (StringUtils.isNotEmpty(upmsUser.getPassword())) {
            passwordNew = RSAUtils.decrypt(upmsUser.getPassword(), RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
            // 不允许直接改密码
            Object md = new SimpleHash("MD5", passwordNew, upmsUser.getSalt(), 1);
            upmsUser.setPassword(md.toString());
            upmsUser.setVerifyPassword(DictConstant.USER_HAS_LOGGED);
        }
//        upmsUser.setPassword(null);
        int count = userService.updateUser(upmsUser);
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequiresPermissions("sys_user_edit")
    @RequestMapping(value = "/updatePassword", method = RequestMethod.POST)
    public Object updatePassword(UpmsUser upmsUser) {
        String passwordNew = RSAUtils.decrypt(upmsUser.getPassword(), RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
        // 不允许直接改密码
        Object md = new SimpleHash("MD5", passwordNew, ByteSource.Util.bytes(upmsUser.getSalt()), 1);
        if (md.toString().equals(UserUtils.getUser().getPassword())) {
            return new UpmsResult(UpmsResultConstant.PASSWORD_REPEAT, 0);
        }
        upmsUser.setPassword(md.toString());
        upmsUser.setVerifyPassword(DictConstant.USER_HAS_LOGGED);
        int count = userService.updateUser(upmsUser);
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequiresPermissions("sys_user_disabled")
    @RequestMapping(value = "/locked", method = RequestMethod.POST)
    public Object locked(UpmsUser upmsUser) {
        int count = userService.updateUser(upmsUser.getUserId(), StatusEnum.FORBIDDEN.getByteVal());
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequiresPermissions("sys_user_enabled")
    @RequestMapping(value = "/unlock", method = RequestMethod.POST)
    public Object unlock(UpmsUser upmsUser) {
        int count = userService.updateUser(upmsUser.getUserId(), StatusEnum.NORMAL.getByteVal());
        return new UpmsResult(UpmsResultConstant.SUCCESS, count);
    }

    @RequestMapping(value = "listCurrentUser", method = RequestMethod.POST)
    public Object listCurrentUser() {
        return new UpmsResult(
                UpmsResultConstant.SUCCESS
                , userService.selectUserInfoById());
    }
}

到此,身份认证及权限管理已完成,如果还要加入缓存机制,还要引入依赖

参考:spring boot学习教程

猜你喜欢

转载自blog.csdn.net/xiaogc_a/article/details/82528505