1、 RBAC模型
RBAC:Role-BaseAccess control 基于角色的访问控制
先在系统中定义不同的角色,不同的角色会有不同的权限,所有用户都会分配到不同的角色中
现在比较全面的是给用户设置用户组,给用户组设置权限。
在分布式系统中,权限系统是独立出来的,这样就可以给其余业务系统做权限管理。
2、授权流程
管理员在权限中心给用户授权:给用户添加角色,给角色添加用户
用户申请某个角色后,审批节点审批后,用户获得角色
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
3、权限框架
3.1SpringBoot 整合 Shiro
1. 添加依赖
<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 实现对 Shiro 的自动化配置 :实际需要主动配置-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
</dependencies>
2.配置类
@Configuration
public class ShiroConfig {
@Bean
public Realm realm() { /**特定安全的DAO,用于身份认证和授权**/
// 创建 SimpleAccountRealm 对象
SimpleAccountRealm realm = new SimpleAccountRealm();
// 添加两个用户。参数分别是 username、password、roles 。
realm.addAccount("admin", "admin", "ADMIN");
realm.addAccount("normal", "normal", "NORMAL");
return realm; }
@Bean//shiro 架构的核心,
public DefaultWebSecurityManager securityManager() {
// 创建 DefaultWebSecurityManager 对象
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置其使用的 Realm
securityManager.setRealm(this.realm());
return securityManager; }
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
// <1> 创建 ShiroFilterFactoryBean 对象,用于创建 ShiroFilter 过滤器
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
// <2> 设置 SecurityManager
filterFactoryBean.setSecurityManager(this.securityManager());
// <3> 设置 URL 们
filterFactoryBean.setLoginUrl("/login"); // 登录 URL
filterFactoryBean.setSuccessUrl("/login_success"); // 登录成功 URL
filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 无权限 URL
// <4> 设置 URL 的权限配置
filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap());
return filterFactoryBean;}
}
@Controller
@RequestMapping("/")
public class SecurityController {
private Logger logger = LoggerFactory.getLogger(getClass());
@GetMapping("/login")
public String loginPage() {
return "login.html";
}
@ResponseBody
@PostMapping("/login")
public String login(HttpServletRequest request) {
// <1> 判断是否已经登录
Subject subject = SecurityUtils.getSubject();
if (subject.getPrincipal() != null) {
return "你已经登录账号:" + subject.getPrincipal();
}
// <2> 获得登录失败的原因
String shiroLoginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
// 翻译成人类看的懂的提示
String msg = "";
if (UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
msg = "账号不存在";
} else if (IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
msg = "密码不正确";
} else if (LockedAccountException.class.getName().equals(shiroLoginFailure)) {
msg = "账号被锁定";
} else if (ExpiredCredentialsException.class.getName().equals(shiroLoginFailure)) {
msg = "账号已过期";
} else {
msg = "未知";
logger.error("[login][未知登录错误:{}]", shiroLoginFailure);
}
return "登录失败,原因:" + msg;
}
@ResponseBody
@GetMapping("/login_success")
public String loginSuccess() { /**省略代码**/ }
@ResponseBody
@GetMapping("/unauthorized")
public String unauthorized() { return "你没有权限"; }
}
//resources/static/login.html 静态页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"/> <br />
密码:<input type="password" name="password"/> <br />
<input type="submit" value="登录"/>
</form>
</body>
</html>
名称 | 功能 |
---|---|
anno | AnnoymousFilter:允许匿名访问,无需登录 |
athc | FormAuthenticationFilter :需要经过认证的用户,才可以访问 |
logout | LogoutFilter :拦截退出操作 |
roles | RolesAuthorizationFilter : 拥有指定角色的用户可以访问 |
pers | PermissionsAuthorizationFilter : 拥有指定权限的用户可以访问 |
注解 | 功能 |
---|---|
@RequireGuest | 等价于anno |
@RequiresAuthentication | 等价于 authc |
@RequiresUser | 等价于User 必须登录 |
@RequireRoles | 等价于roles |
@RequiresPermissions | 等价于prems |
用户的登录请求会被配置到 Shiro FormAuthenticationFilter 过滤器进行拦截,进行用户的身份认证.
- FormAuthenticationFilter 解析请求的username password,创建UsernamePasswordToken对象
- 然后调用 SecurityManager的 login 方法,进行登录操作,进行身份校验
- 在这内部会调用Realm的 getAuthenticationInfo 方法进行认证
- 成功由FormAuthenticationFilter 重定向到 GET loginSuccess 地址
- 失败会将认证失败的原因设置到请求的attribute中,继续请求到login地址上,就可以获取到失败的原因提示给用户
3.2 SpringBoot 整合springSecurity
Spring 是一个非常流行和成功的Java框架.SpringSecurity是基于Spring框架,提供了一套Web应用安全的完整解决方案,SpringSecurity 其实就是用fiter,对请求的路径进行过滤.
(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
1.添加依赖
<!--MVC 支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--security 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jjwt token-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.配置
WebSecurityConfig.Java
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启对SpringSecrity注解的方法进行权限验证
//@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 登录成功处理逻辑
*/
@Autowired
private CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;
/**
* //登录失败处理逻辑
*/
@Autowired
private CustomizeAuthenticationFailureHandler authenticationFailureHandler;
/**
* 权限拒绝处理逻辑
*/
@Autowired
private CustomizeAccessDeniedHandler accessDeniedHandler;
/*匿名用户访问无权限资源时的异常
*/
@Autowired
private CustomizeAuthenticationEntryPoint authenticationEntryPoint;
/**
* 会话失效(账号被挤下线)处理逻辑
*/
@Autowired
private CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
/**
* 登出成功处理逻辑
*/
@Autowired
private CustomizeLogoutSuccessHandler logoutSuccessHandler;
/**
* 访问决策管理器
*/
@Autowired
private CustomizeAccessDecisionManager accessDecisionManager;
/**
* 实现权限拦截
*/
@Autowired
private CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
private UserDetailsServiceImpl userDetailsService;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
@Autowired
JwtAuthenticationTokenFilter authenticationTokenFilter;
// @Bean
// public UserDetailsService userDetailsService() {
// //获取用户账号密码及权限信息
// return new UserDetailsServiceImpl();
// }
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 设置默认的加密方式(强hash方式加密)
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置认证方式等
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
// auth.userDetailsService(userDetailsService());
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().//允许跨域
csrf().disable()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
// 基于token,所以不需要session【但禁用后,将导致UserDetailsService每次都会执行,无法缓存用户信息】
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
//过滤请求
.authorizeRequests()
// .antMatchers("/api/login","/api/info", "/register", "/captchaImage").anonymous()
.antMatchers("/api/**", "/**","/register", "/captchaImage").anonymous()
// // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
//因为SpringSecurity使用X-Frame-Options防止网页被Frame。所以需要关闭为了让后端的接口管理的swagger页面正常显示
.headers().frameOptions().disable();
http.logout().logoutUrl("/api/logout").logoutSuccessHandler(logoutSuccessHandler);
//添加JWT filter
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
// http.addFilterBefore(corsFilter, LogoutFilter.class);
}
}
详细内容可以查询Gitee上源码:https://gitee.com/leiyuee/manage.git
通过继承WebSecurityConfigurerAdapter
- 重写其 configure(AuthenticationManagerBuilder auth)方法 实现 AuthenticationManager 认证管理器:在UserDetailsServiceImpl实现UserDetailsService 重写loadUserByUsername方法校验用户—数据库交互
- 重写configure(HttpSecurity http)方法,配置URL的权限控制