搞点事情 03-权限设置

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>
名称功能
annoAnnoymousFilter:允许匿名访问,无需登录
athcFormAuthenticationFilter :需要经过认证的用户,才可以访问
logoutLogoutFilter :拦截退出操作
rolesRolesAuthorizationFilter : 拥有指定角色的用户可以访问
persPermissionsAuthorizationFilter : 拥有指定权限的用户可以访问
注解功能
@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的权限控制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值