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使用authc2不使用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
;
@Configuration
public class ShiroConfig {
@Bean
MyRealm
myRealm(){
MyRealm myRealm
= new MyRealm();
myRealm
.setAuthenticationTokenClass(AuthenticationToken
.class);
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");
map
.put("/swagger-ui.html", "anon");
map
.put("/swagger-resources/**", "anon");
map
.put("/v2/**", "anon");
map
.put("/webjars/**", "anon");
// map
.put("/configuration/ui", "anon");
// 开放以api开始的接口,不使用鉴权
map
.put("/api/**", "anon");
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
.*
;
public class MyRealm extends AuthorizingRealm {
@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
);
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
;
public class MyCredentialsMatcherUtil extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token
, AuthenticationInfo info
) {
String originalPassword
= String
.valueOf((char[]) token
.getCredentials());
String sqlOriginalPassword
=(String
)info
.getCredentials();
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
.*
;
@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
;
@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
.*
;
@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