非starter方式实现springboot与shiro集成
Apache Shiro是什么?
Apache Shiro是一个强大且易用的Java安全框架,能够执行身份验证、授权、加密和会话管理等。
Apache Shiro的主要API
Subject
Subject即“当前操作用户”。但是,在Shiro中,Subject这一概念并不是"帐户",而是与shrio交互的当前应用。
SecurityManager
SecurityManager是Shiro框架的核心,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm
-
Realm是安全数据源,其中包含认证和授权数据。
也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
-
当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。
Shiro内置了很多可用的安全数据源(又名目录)的Realm, 如果内置的Realm不能满足需求,可以自定义Realm实现。
Apache Shiro与Springboot集成
依赖引入
<!-- Shiro核心包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Shiro Web支持包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Shiro与Spring的集成包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
开发集成shiro的配置类
开发一个集成shiro的spring配置类
@Configuration
public class ShiroConfigure {
private static final Logger LOG = LoggerFactory.getLogger(ShiroConfigure.class);
//注册跨域过滤器
//开发和配置安全数据源
//配置Shiro安全管理器
//配置shiro过滤器拦截规则定义
//配置shiro过滤器
//配置shiro生命周期管理器
}
注册跨域过滤器
shiro过滤器会优先于springboot拦截请求,springboot的跨域设置将不起作用,因此需要专门注册跨域过滤器并设置其为过滤器链中的第一个。
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// 允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// 允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求(options请求)的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(1L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("*");
/*
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
*/
//设置允许跨域的路径
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
// 设过滤器的优先级
bean.setOrder(0);
return bean;
}
开发和配置安全数据源
/*
* 配置安全数据源Realm
* 此处必须要命名“authorizer”,
* 因为shiro会自动寻找名称为“authorizer”的、并实现接口Authorizer的对象,
* 而AuthorizingRealm正是接口Authorizer的实现类
*/
@Bean("authorizer")
@Autowired
public AuthorizingRealm saftyRealm(ShiroService shiroService) {
return new AuthorizingRealm() {
/**
* 获取当事人(当前用户)授权信息,shiro在检查访问是否经过授权时需要通过该方法获取授权信息。
* 参数PrincipalCollection 当事人集合
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/*
* 获取登录用户帐号
*/
CurrUser currUser = (CurrUser) principalCollection.getPrimaryPrincipal();// 获取首要(第一)当事人
/*
* 创建授权信息对象
*/
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
/*
* 查询用户权限,并将权限放入授权信息对象中
*/
List<Module> moduleList = shiroService.getModulesOfUser(currUser.getUserId());
for (Module module : moduleList) {
simpleAuthorizationInfo.addStringPermission(String.valueOf(module.getM_id()));
}
// System.out.println(currUser.getUserId()+"->"+simpleAuthorizationInfo.getStringPermissions());
/*
* 返回授权信息
*/
return simpleAuthorizationInfo;
}
/**
* 获取认证信息(即包含当前用户帐户和合法密码等信息)
* shiro在登录认证时需要通过该方法获取认证信息。
* 参数 AuthenticationToken 认证令牌(如:一组用户名和密码就是一个认证令牌)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String userId = (String) token.getPrincipal();// 获得当事人(当前用户账号)
User user = shiroService.getUser(userId);
/*
如果不存在前用户信息,返回null
*/
if (user == null) {
return null;
}
/*
创建当前用户
*/
CurrUser currUser = new CurrUser(user.getU_id(),user.getU_name());
/*
创建认证信息,三个构造参数含义依次如下:
参数1:principal当前用户
参数2:credentials认证凭证(如:口令、密码等)
参数3:realm名称
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currUser, user.getU_pwd(), this.getName());
/*
返回认证信息
*/
return info;
}
} ;
}
配置Shiro安全管理器
@Bean
@Autowired
public org.apache.shiro.mgt.SecurityManager shiroSecurityManager(Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
配置shiro过滤器拦截规则定义
@Bean @Autowired
public ShiroFilterChainDefinition shiroFilterChainDefinition(ShiroService shiroService) {
DefaultShiroFilterChainDefinition chainDefinition =
new DefaultShiroFilterChainDefinition();
/*
可匿名访问资源
*/
chainDefinition.addPathDefinition("/css/**", "anon");
chainDefinition.addPathDefinition("/elementui/**", "anon");
chainDefinition.addPathDefinition("/js/**", "anon");
chainDefinition.addPathDefinition("/safty/login/**", "anon");
//加载动态权限
List<Module> moduleList = shiroService.getAllSubModules();
String PREMISSION_FORMAT = "authc,perms[{0}]";
//动态权限设置
for(Module module:moduleList) {
if(StringUtils.isEmpty(module.getM_url())) {
continue;
}
chainDefinition.addPathDefinition(module.getM_url().replace("index.html", "**"), MessageFormat.format(PREMISSION_FORMAT, String.valueOf(module.getM_id())));
}
//其它资源必须经过认证
chainDefinition.addPathDefinition("/**", "authc");
LOG.debug("=====Shiro安全规则=======================================================================");
LOG.debug(chainDefinition.getFilterChainMap().toString());
LOG.debug("=====Shiro安全规则=======================================================================");
return chainDefinition;
}
}
配置shiro过滤器
@Bean
@Autowired
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) throws Exception {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置登录url,shiro发现未认证时自动重定向到该url,如果是前后端分离式开发该url应当发送json串。
shiroFilterFactoryBean.setLoginUrl("/loginTo");
//设置未授权url,shiro发现未授权时自动重定向到该url,如果是前后端分离式开发该url应当发送json串。
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizedTo");
shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
return shiroFilterFactoryBean;
}
配置shiro生命周期管理器
@Bean
public org.apache.shiro.spring.LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new org.apache.shiro.spring.LifecycleBeanPostProcessor();
}
登录示例
@RestController
@RequestMapping("/safty/login")
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/user")
public Result execLogin(@RequestBody UserDto userDto,HttpSession session) {
try {
Subject subject = SecurityUtils.getSubject();
//创建登录令牌
UsernamePasswordToken usernamePasswordToken =
new UsernamePasswordToken(userDto.getU_id(),userDto.getU_pwd());
//登录,该方法声明抛出异常,可以捕获异常,并进行应对处理
subject.login(usernamePasswordToken);
//是否通过认证
if(subject.isAuthenticated()) {
//获得当前用户信息
CurrUser currUser = (CurrUser)subject.getPrincipal();
//将当前用户放入Session。注:这里的Session是由Shiro提供的。
subject.getSession().setAttribute(Constants.SESSINON_ATTR_NAME_CURR_USER, currUser);
return Result.successResult("登录成功!");
}
return Result.failResult("登录失败!");
} catch (UnknownAccountException e) {
return Result.failResult("用户名不存在!");
} catch (IncorrectCredentialsException e) {
return Result.failResult("账户密码不正确!");
} catch (LockedAccountException e) {
return Result.failResult("用户名被锁定 !");
}catch (Exception e) {
e.printStackTrace();
return Result.failResult("系统错误!");
}
}
}
退出示例
@RestController
@RequestMapping("/safty/logout")
public class LoginController {
@Autowired
private LoginService loginService;
@DeleteMapping("/logout")
public ResultDto login_authentication() {
try {
//注销
SecurityUtils.getSubject().logout();
return Result.successResult();
} catch (Exception e) {
return Result.failResult("退出失败!");
}
}
}
测试
上述配置和相关代码开发完成后,即可进行测试调试。