ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot整合Shiro多realm登录配置,另外附俩个教程

2020-05-06 09:40:02  阅读:748  来源: 互联网

标签:return SpringBoot 登录 俩个 user new realm public


SpringBoot整合Shiro,通过用户、角色、权限三者关联实现权限管理

本篇文章主要介绍 Shiro 多 realm,根据不同的登录类型指定不同的 realm。

所谓免密登录,就是区别正常的密码登录。比如,我现在要实现第三方登录,当验证了是李四,现在要让他通过 shiro 的 subject.login(),但是不知道他的密码(密码加密了),我们不能拿数据库里的密码去登录,除非重新写 Realm。

所以需要多个 Realm,一个是密码登录(shiro会根据用户的输入的密码和加密方法加密后比较);一个免密登录(允许使用数据库密码登录,shiro不进行任何加密)。

实现的过程简单说下:

重写 UsernamePasswordToken,加一个 loginType 属性,subject.login() 的时候传入 loginType; 重写 ModularRealmAuthenticator 中的 doAuthenticate() 方法,根据传进来的 loginType 来指定使用哪个 Realm。

一、Shiro 配置

1.两个 Realm

NormalRealm.java 密码登录的 Realm

/**
 * 默认的realm
 *
 * @author lp
 */
@Slf4j
public class NormalRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private LocaleMessageUtil localeMessageUtil;
    /**
     * 认证信息(身份验证) Authentication 是用来验证用户身份
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        log.info("认证-->MyShiroRealm.doGetAuthenticationInfo()");
        //1.验证用户名
        User user = null;
        String loginName = (String) token.getPrincipal();
        if (Validator.isEmail(loginName)) {
            user = userService.findByEmail(loginName);
        } else {
            user = userService.findByUserName(loginName);
        }
        if (user == null) {
            //用户不存在
            log.info("用户不存在! ", loginName, token.getCredentials());
            return null;
        }
        //2.首先判断是否已经被禁用已经是否已经过了10分钟
        Date loginLast = DateUtil.date();
        if (null != user.getLoginLast()) {
            loginLast = user.getLoginLast();
        }
        Long between = DateUtil.between(loginLast, DateUtil.date(), DateUnit.MINUTE);
        if (StringUtils.equals(user.getLoginEnable(), TrueFalseEnum.FALSE.getDesc()) && (between < CommonParamsEnum.TEN.getValue())) {
            log.info("账号已锁定!", loginName, token.getCredentials());
            throw new LockedAccountException(localeMessageUtil.getMessage("code.admin.login.disabled"));
        }
        userService.updateUserLoginLast(user, DateUtil.date());
        //3.封装authenticationInfo,准备验证密码
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user, // 用户名
                user.getUserPass(), // 密码
                ByteSource.Util.bytes("sens"), // 盐
                getName() // realm name
        );
        System.out.println("realName:" + getName());
        return authenticationInfo;
    }
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("授权-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        List<Role> roles = roleService.listRolesByUserId(user.getUserId());
        for (Role role : roles) {
            authorizationInfo.addRole(role.getRole());
            List<Permission> permissions = permissionService.listPermissionsByRoleId(role.getId());
            for (Permission p : permissions) {
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }
}

FreeRealm.java 密码不加密的 Realm

/**
 * 免密登录,输入的密码和原密码一致
 *
 * @author lp
 */
@Slf4j
public class FreeRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private LocaleMessageUtil localeMessageUtil;
    /**
     * 认证信息(身份验证) Authentication 是用来验证用户身份
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.验证用户名
        User user = null;
        String loginName = (String) token.getPrincipal();
        if (Validator.isEmail(loginName)) {
            user = userService.findByEmail(loginName);
        } else {
            user = userService.findByUserName(loginName);
        }
        if (user == null) {
            //用户不存在
            log.info("第三方登录,用户不存在! 登录名:{}, 密码:{}", loginName,token.getCredentials());
            return null;
        }
        //3.封装authenticationInfo,准备验证密码
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user, // 用户名
                user.getUserPass(), // 密码
                null,
                getName() // realm name
        );
        return authenticationInfo;
    }
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        List<Role> roles = roleService.listRolesByUserId(user.getUserId());
        for (Role role : roles) {
            authorizationInfo.addRole(role.getRole());
            List<Permission> permissions = permissionService.listPermissionsByRoleId(role.getId());
            for (Permission p : permissions) {
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }
}

2.ShiroConfig

/**
 * @author lp
 */
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/upload/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/admin/login", "anon");
        filterChainDefinitionMap.put("/admin/getLogin", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/admin/**", "authc");
        filterChainDefinitionMap.put("/backup/**", "authc");
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
        shiroFilterFactoryBean.setLoginUrl("/admin/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        return shiroFilterFactoryBean;
    }
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(modularRealmAuthenticator());
        List<Realm> realms = new ArrayList<>();
        //密码登录realm
        realms.add(normalRealm());
        //免密登录realm
        realms.add(freeRealm());
        securityManager.setRealms(realms);
        return securityManager;
    }
    /**
     * 系统自带的Realm管理,主要针对多realm
     * */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        //自己重写的ModularRealmAuthenticator
        UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }
    /**
     * 需要密码登录的realm
     *
     * @return MyShiroRealm
     */
    @Bean
    public NormalRealm normalRealm() {
        NormalRealm normalRealm = new NormalRealm();
        normalRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return normalRealm;
    }
    /**
     * 免密登录realm
     *
     * @return MyShiroRealm
     */
    @Bean
    public FreeRealm freeRealm() {
        FreeRealm realm = new FreeRealm();
        //不需要加密,直接用数据库密码进行登录
        return realm;
    }
    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     *  所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(10);//散列的次数,md5("")
        return hashedCredentialsMatcher;
    }
    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    /** * Shiro生命周期处理器 * @return */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
}

多 Realm 模式需要重写 ModularRealmAuthenticator

3.重写ModularRealmAuthenticator

UserModularRealmAuthenticator.java

/**
 * @author lp
 */
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        System.out.println("UserModularRealmAuthenticator:method doAuthenticate() execute ");
        // 判断getRealms()是否返回为空
        assertRealmsConfigured();
        // 强制转换回自定义的CustomizedToken
        UserToken userToken = (UserToken) authenticationToken;
        // 登录类型
        String loginType = userToken.getLoginType();
        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登录类型对应的所有Realm
        List<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            if (realm.getName().contains(loginType)) {
                typeRealms.add(realm);
            }
        }
        // 判断是单Realm还是多Realm
        if (typeRealms.size() == 1){
            System.out.println("doSingleRealmAuthentication() execute ");
            return doSingleRealmAuthentication(typeRealms.get(0), userToken);
        }
        else{
            System.out.println("doMultiRealmAuthentication() execute ");
            return doMultiRealmAuthentication(typeRealms, userToken);
        }
    }
}

4.枚举类 LoginType

LoginType.java

/**
 * @author lp
 */
public enum LoginType {
    /**
     * 密码登录
     */
    NORMAL("Normal"),
    /**
     * 免密码登录
     */
    FREE("Free");
    private String desc;
    LoginType(String desc) {
        this.desc = desc;
    }
    public String getDesc() {
        return desc;
    }
}

5.自定义UsernamePasswordToken

UserToken.java

/**
 *
 * 自定义UsernamePasswordToken
 * 必须传loginType
 *
 * @author lp
 */
@Data
public class UserToken extends UsernamePasswordToken {
    private String loginType;
    public UserToken() {
    }
    public UserToken(final String username, final String password,
                     final String loginType) {
        super(username, password);
        this.loginType = loginType;
    }
}

二、登录
1.正常的密码登录

@PostMapping(value = "/getLogin")
@ResponseBody
public JsonResult getLogin(@ModelAttribute("loginName") String loginName, @ModelAttribute("loginPwd") String loginPwd) {
        Subject subject = SecurityUtils.getSubject();
        UserToken token = new UserToken(loginName, loginPwd, LoginType.FREE.getDesc());
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                //登录成功,修改登录错误次数为0
                User user = (User) subject.getPrincipal();
                userService.updateUserLoginNormal(user);
                return new JsonResult(ResultCodeEnum.SUCCESS.getCode(),"登录成功");
            }
        } catch (UnknownAccountException e) {
            ...
        }
       ...
    }

2.第三方登录,授权成功后,免密登录

/**
 * @author lp
 */
@Controller
@Slf4j
public class AuthController {
    @Autowired
    private QQAuthService qqAuthService;
    @Autowired
    private UserService userService;
    @Autowired
    private ThirdAppBindService thirdAppBindService;
    @Autowired
    private LogService logService;
    /**
     * 第三方授权后会回调此方法,并将code传过来
     *
     * @param code code
     * @return
     */
    @GetMapping("/oauth/qq/callback")
    public String oauthByQQ(@RequestParam(value = "code") String code, HttpServletRequest request) {
        Response<String> tokenResponse = qqAuthService.getAccessToken(code);
        if (tokenResponse.isSuccess()) {
            Response<String> openidResponse = qqAuthService.getOpenId(tokenResponse.getData());
            if (openidResponse.isSuccess()) {
                //根据openId去找关联的用户
                ThirdAppBind bind = thirdAppBindService.findByAppTypeAndOpenId(BindTypeEnum.QQ.getValue(), openidResponse.getData());
                if (bind != null && bind.getUserId() != null) {
                    //执行Login操作
                    User user = userService.findByUserId(bind.getUserId());
                    if (user != null) {
                        Subject subject = SecurityUtils.getSubject();
                        UserToken userToken = new UserToken(user.getUserName(), user.getUserPass(), LoginType.FREE.getDesc());  
                        try {
                            subject.login(userToken);
                        } catch (Exception e) {
                            e.printStackTrace();
                            log.error("第三方登录(QQ)免密码登录失败, cause:{}", e);
                            return "redirect:/admin/login";
                        }
                        logService.saveByLog(new Log(LogsRecord.LOGIN, LogsRecord.LOGIN_SUCCESS + "(QQ登录)", ServletUtil.getClientIP(request), DateUtil.date()));
                        log.info("用户[{}]登录成功(QQ登录)。", user.getUserDisplayName());
                        return "redirect:/admin";
                    }
                }
            }
        }
        return "redirect:/admin/login";
    }
}

对于springboot不了解的小伙伴提供俩个教程

Java微服务SpringBoot开发新闻资讯项目实战课程

零基础入门SpringBoot到高级实战课程

标签:return,SpringBoot,登录,俩个,user,new,realm,public
来源: https://blog.csdn.net/qq_35238997/article/details/105918492

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有