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 "未授权";
}
}
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.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.youkuaiyun.com/bicheng4769/article/details/86668209
[4]https://blog.youkuaiyun.com/taojin12/article/details/88343990
[5]https://segmentfault.com/a/1190000019440231
[6]https://blog.youkuaiyun.com/afsvsv/article/details/86639482
配置swagger显示环境dev,test
[]https://blog.youkuaiyun.com/SSHH_ZHU/article/details/104286169
[]https://www.cnblogs.com/jjsir/p/13613437.html
[]https://blog.youkuaiyun.com/qq_21537671/article/details/107280447
[]https://www.jb51.net/article/152130.htm