Spirngboot模块系列:安全认证Shiro

1 配置

  • pom.xml
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • 配置接口(路由)鉴权
    接口可以使用鉴权也可不使用鉴权
序号是否使用鉴权标志
1使用authc
2不使用anon
  • 使用ShiroFilterFactoryBean配置路由权限
  • 自定义权限认证逻辑:继承AuthorizingRealm类
package com.company.ddd.infrastructure.config;

import com.company.ddd.infrastructure.util.*;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;

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

/**
 * @author xindaqi
 * @since 2020-10-08
 */
@Configuration
public class ShiroConfig {

    @Bean 
    MyRealm myRealm(){
        MyRealm myRealm = new MyRealm();
        // 设置鉴权token,前端登录时传递的参数,进行初始化
        myRealm.setAuthenticationTokenClass(AuthenticationToken.class);
       // 自定义密码校验,继承SimpleCredentialsMatcher
        myRealm.setCredentialsMatcher(new MyCredentialsMatcherUtil());
        return myRealm;
    }

    @Bean
    SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean 
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauthorizedurl");
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/doLogin", "anon");
        /**
         * Swagger2开放,不使用鉴权
         */
        map.put("/swagger-ui.html", "anon");
        map.put("/swagger-resources/**", "anon");
        map.put("/v2/**", "anon");
        map.put("/webjars/**", "anon");
        // map.put("/configuration/security", "anon");
        // map.put("/configuration/ui", "anon");
        // 开放以api开始的接口,不使用鉴权
        map.put("/api/**", "anon");
        // 对除了api以外的接口,全部鉴权
        map.put("/**", "authc");

        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

    
}

2 登录认证

配置接口鉴权之后,调用接口时需要先登录或者其他鉴权功能,可以自定,这里以登录为例,通过登录service查询数据库用户名和密码,将登陆时的前端传的用户名和密码与数据库比对,若数据库的密码是通过加密的,需要自定义密码校验规则,如使用security生成密码和校验密码。

package com.company.ddd.infrastructure.util;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import com.company.ddd.interfaces.dto.user.*;
import com.company.ddd.application.service.*;

/**
 * @author xindaqi
 * @since 2020-10-08
 */
public class MyRealm extends AuthorizingRealm {
	// 数据库查询Service,登录时查询用户和密码
    @Autowired
    private IUserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        LoginInputDTO params = new LoginInputDTO();
        params.setUsername(username);
        // params.setPassword();
        LoginOutputDTO loginOutputDTO = userService.login(params);
        String usernameInDb = loginOutputDTO.getUsername();
        String passwordInDb = loginOutputDTO.getPassword();
        if(!usernameInDb.equals(username)) {
            throw new UnknownAccountException("账号不存在");
        }
        return new SimpleAuthenticationInfo(username, passwordInDb, getName());
    }
}

3 自定义密码认证

由于shiro没有提供加密的密码校验,所以数据库存储了加密的密码,就要通过继承SimpleCredentialsMatcher,实现密码校验。

package com.company.ddd.infrastructure.util;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author xindaqi
 * @since 2020-10-11
 */
public class MyCredentialsMatcherUtil extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    // Password from user login. 
    String originalPassword = String.valueOf((char[]) token.getCredentials());
    // Password in database
    String sqlOriginalPassword=(String)info.getCredentials();
    // 通过security的BCryptPasswordEncoder进行密码校对
    BCryptPasswordEncoder pwdCmp = new BCryptPasswordEncoder();
    Boolean cmpRes = pwdCmp.matches(originalPassword,sqlOriginalPassword);
    return cmpRes;
    }

}

4 登录

package com.company.ddd.interfaces.facade;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.company.ddd.domain.common.vo.*;
import com.company.ddd.interfaces.dto.user.*;
import com.company.ddd.application.service.*;

/**
 * @author xindaqi
 * @since 2020-10-08
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
public class Loginout {

    static Logger logger = LoggerFactory.getLogger(Loginout.class);

    @RequestMapping(value = "/doLogin", method = RequestMethod.POST)
    public ResponseVO login(@RequestBody LoginInputDTO params){
        Subject subject = SecurityUtils.getSubject();
        if (StringUtils.isEmpty(params.getUsername()) || StringUtils.isEmpty(params.getPassword())){
            return ResponseVO.empty();
        }
        
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
            params.getUsername(),
            params.getPassword()
        );
        try {
            subject.login(usernamePasswordToken);
        }catch (UnknownAccountException e){
            logger.error("用户名不存在!");
            return ResponseVO.empty();
        }catch (AuthenticationException e){
            logger.error("账号或密码错误!");
            return ResponseVO.empty();
        }catch (AuthorizationException e) {
            logger.error("没有权限!");
            return ResponseVO.empty();
        }
        return ResponseVO.ok();
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String loginStr(){
        return "请登录";
    }

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public String test(){
        return "未授权";
    }


    
}

在这里插入图片描述

图4.1 登录

5 密码加密:security

5.1 security配置

package com.company.ddd.infrastructure.config;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;

/**
 * @author xindaqi
 * @since 2020-10-11
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoderBCrypt() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/**")// 开放所有接口
            .permitAll()
            .and()
            .csrf()
            .disable();
    }

}

5.2 注册及登录测试

package com.company.ddd.interfaces.facade;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;

import com.company.ddd.domain.common.vo.*;
import com.company.ddd.interfaces.dto.user.*;
import com.company.ddd.application.service.*;

/**
 * @author xindaqi
 * @since 2020-10-06
 */

@CrossOrigin(origins = "*", maxAge=3600)
@RestController
@RequestMapping("/api/user")
@Api(tags = "人员配置")
public class User{

    static Logger logger = LoggerFactory.getLogger(User.class);

    @Autowired
    private BCryptPasswordEncoder passwordEncoderBCrypt;

    @Autowired
    private IUserService userService;

    @RequestMapping(value = "/register", method= RequestMethod.POST)
    @ApiImplicitParam(name = "params", value = "用户信息", dataType = "RegisterUserInputDTO", paramType = "body")
    @ApiOperation("注册会员")
    public ResponseVO registerUser(@RequestBody RegisterUserInputDTO params) {
    	// 密码加密
        String passwordEncoder = passwordEncoderBCrypt.encode(params.getPassword());
        return ResponseVO.ok(passwordEncoder);

    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ApiImplicitParam(name = "params", value = "用户信息", dataType = "LoginInputDTO", paramType = "body")
    @ApiOperation("登录测试")
    public ResponseVO loginUser(@RequestBody LoginInputDTO params) {
        LoginOutputDTO loginOutputDTO = userService.login(params);
        String pwdInDb = loginOutputDTO.getPassword();
        String username = params.getUsername();
        String password = params.getPassword();
        // 密码校对
        Boolean passwordFlag = passwordEncoderBCrypt.matches(password, pwdInDb);
        if(passwordFlag) {
            return ResponseVO.ok();
        }else{
            return ResponseVO.empty();
        }
    }
}

在这里插入图片描述

图5.1 注册

在这里插入图片描述

图5.2 登录

5.3 security密码加密说明

  • security只支持单向的密码加密功能,不支持密码单独解密
    即同一个明码字符串,加密后的密码不同
  • 密码校对通过matches方法,参数为(明码, 密码)

6 小结

  • shiro登录一次,其他需要鉴权的接口均可直接访问
  • shiro加密的密码校验,需要自定义实现
  • security可直接生成加密密码,并支持自校验
  • security不支持单独解密

【参考文献】
[1]https://www.jianshu.com/p/7f724bec3dc3
[2]https://www.imooc.com/qadetail/299768?lastmedia=1
[3]https://blog.csdn.net/bicheng4769/article/details/86668209
[4]https://blog.csdn.net/taojin12/article/details/88343990
[5]https://segmentfault.com/a/1190000019440231
[6]https://blog.csdn.net/afsvsv/article/details/86639482
配置swagger显示环境dev,test
[]https://blog.csdn.net/SSHH_ZHU/article/details/104286169
[]https://www.cnblogs.com/jjsir/p/13613437.html
[]https://blog.csdn.net/qq_21537671/article/details/107280447
[]https://www.jb51.net/article/152130.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值