权限系统设计模型以及SpringSecurity框架实现

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。
特别要指出的是他们不能在WAR 或 EAR 级别进行移植。
这样,如果更换服务器环境,就要,在新的目标环境进行大量的工作,对应用系统进行重新配置安全。
使用Spring Security 解决了这些问题,也提供很多有用的,完全可以指定的其他安全特性。
可能知道,安全包括两个主要操作。
第一个被称为“认证”,是为用户建立一个他所声明的主体。主体一般是指用户,设备或可以在系统中执行动作的其他系统。
第二个叫“授权”,指的是一个用户能否在应用中执行某个操作,在到达授权判断之前,身份的主体已经由身份验证过程建立。
这些概念是通用的,不是Spring Security特有的。
在身份验证层面,Spring Security广泛支持各种身份验证模式,这些验证模型绝大多数都由第三方提供,或者正在开发的有关标准机构提供的,例如 Internet Engineering Task Force.
作为补充,Spring Security 也提供了自己的一套验证功能。
Spring Security 目前支持认证一体化如下认证技术:
1、HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)
2、HTTP Digest authentication headers (一个基于IEFT RFC 的标准)
3、HTTP X.509 client certificate exchange (一个基于IEFT RFC 的标准)
4、LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)
5、Form-based authentication (提供简单用户接口的需求)
6、OpenID authentication
7、Computer Associates Siteminder
8、JA-SIG Central Authentication Service (CAS,这是一个流行的开源单点登录系统)
9、Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)

权限系统设计模型介绍

参考文章 权限系统设计模型分析(DAC,MAC,RBAC,ABAC)

SpringSecurity糅合Role认证与Authority实现权限系统

这个demo演示如何使用springsecurity的两种验证,并不对应任何权限设计模型。在基于Role和Authority两种验证授权的方式下灵活应对各种业务需求。
此demo的Git地址

基本的 用户-角色 表设计参考

在这里插入图片描述
四个个User:
aa : ROLE_ADMIN
bb : ROLE_NORMAL
cc : AUTH_PUSH
dd : AUTH_PUSH 和 AUTH_POP

这样设计权限都是根据一个个角色分配的,用户要使用某个操作的权限就需要拥有对应的角色。

Gradle引入SpringSecurity依赖

    implementation 'org.springframework.boot:spring-boot-starter-security'
   
    testImplementation 'org.springframework.security:spring-security-test'

测试用的Controller

Action Role/Auth
/ anymous
/sayhi/{id} anymous
/sayhello/{name} ROLE_ADMIN
/goodbye/{name} ROLE_ADMIN / ROLE_NORMAL
/push AUTH_PUSH / AUTH_POP
/pop AUTH_POP

为了表达出Spring Security配置的灵活性,/sayhello action在SecurityConfig配置类中配置,/goodbyte action在方法前用注解配置(需要开启@EnableGlobalMethodSecurity (prePostEnabled = true)
)。

@Controller
public class TestController {

    public static volatile Integer countNumber =0;

    @RequestMapping("/")
    public String HomeIndexPage(){
        return "/Views/Index";
    }
    @RequestMapping("/loginPage")
    public String LoginPage(){
        return "/Views/Login";
    }

    @RequestMapping("/loginProc")
    public String loginProcAction(){
        System.out.println("come in Login processing");
        return "/";
    }
    @ResponseBody
    @RequestMapping("/sayhi/{id}")
    public JSONObject firstAction(@Null @PathVariable("id")String id){
        if(id==null)
            id = "001";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Id",id);
        synchronized (countNumber) {
        jsonObject.put("number", ++countNumber);
            jsonObject.put("Action", "sayhi+id");
        }
        return jsonObject;
    }

    @ResponseBody
    @RequestMapping("/sayhello/{name}")
    public JSONObject secondAction(@Nullable @PathVariable("name")String id){
        if(id==null)
            id = "001";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Id",id);
        synchronized (countNumber) {
            jsonObject.put("number", ++countNumber);
            jsonObject.put("Action", "sayhello+name");
        }
        return jsonObject;
    }

    @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_NORMAL')")
    @ResponseBody
    @RequestMapping("/goodbye/{name}")
    public JSONObject thirdAction(@PathVariable("name")String name){
        if(name==null)
            name = "001";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Name",name);
        synchronized (countNumber) {
            jsonObject.put("number", ++countNumber);
            jsonObject.put("Action", "goodbyte+name");
        }
        return jsonObject;
    }

    @PreAuthorize("hasAnyAuthority('AUTH_PUSH','AUTH_POP')")
    @ResponseBody
    @RequestMapping("/push")
    public JSONObject ForthAction(){
        String name = "Someone";
        if(name==null)
            name = "001";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Name",name);
        synchronized (countNumber) {
            jsonObject.put("number", ++countNumber);
            jsonObject.put("Action", "goodbyte+name");
        }
        return jsonObject;
    }

//    @PreAuthorize("hasAnyAuthority('AUTH_POP')")
    @ResponseBody
    @RequestMapping("/pop")
    public JSONObject thirdAction(){
        String name = "a person";
        if(name==null)
            name = "001";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Name",name);
        synchronized (countNumber) {
            jsonObject.put("number", ++countNumber);
            jsonObject.put("Action", "goodbyte+name");
        }
        return jsonObject;
    }
}

SpringSecurity配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity (prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServer userDetailsServer;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        super.configure(http);
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/sayhello/*").hasAnyRole("ADMIN")
                .antMatchers("/pop").hasAnyAuthority("AUTH_POP")
//                .antMatchers("/**").fullyAuthenticated()
//                .and().httpBasic()
                .and().logout()
                .and().rememberMe()
                .and().formLogin();
    }

    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//        super.configure(auth);
//        auth.inMemoryAuthentication().withUser("cc").password("698d51a19d8a121ce581499d7b701668").accountLocked(true).authorities();
        System.out.println("current this configure auto...");

        auth.userDetailsService(userDetailsServer).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return GetMD5(rawPassword.toString());
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                String tpwd = GetMD5(rawPassword.toString());
                if (tpwd.equals(encodedPassword))
                    return true;
                else
                    return false;
            }
        });
    }

    public static String GetMD5(String token)
    {
        String str=new String();
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD5");
            byte[] md5 = md.digest(token.getBytes());
            for(int i=0;i<16;i++)
            {
                String c=new String();
                c+=md5[i];
                int a=0;
                a= Integer.parseInt(c, 10);
                String s= Integer.toHexString(a);
                if(s.length()>2)
                    str+=s.substring(s.length()-2);
                else if(s.length()==1)
                    str+="0"+s;
                else
                    str+=s;
            }
            System.out.println(str);
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return str;
    }
}

UserDetailsService用户权限验证业务类

@Service
public class UserDetailsServer implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if(username.equals("aa")) {
            List<GrantedAuthority> grantedAuthorities = new LinkedList<>();
            ((LinkedList<GrantedAuthority>) grantedAuthorities).offer(new SimpleGrantedAuthority("ROLE_ADMIN"));
            return new org.springframework.security.core.userdetails.User(username, "698d51a19d8a121ce581499d7b701668", grantedAuthorities);
        } else if (username.equals("bb")) {
            List<GrantedAuthority> grantedAuthorities = new LinkedList<>();
            ((LinkedList<GrantedAuthority>) grantedAuthorities).offer(new SimpleGrantedAuthority("ROLE_NORMAL"));
            return new org.springframework.security.core.userdetails.User(username, "698d51a19d8a121ce581499d7b701668", grantedAuthorities);
        }else if (username.equals("cc")) {
            List<GrantedAuthority> grantedAuthorities = new LinkedList<>();
            ((LinkedList<GrantedAuthority>) grantedAuthorities).offer(new SimpleGrantedAuthority("AUTH_PUSH"));
            return new org.springframework.security.core.userdetails.User(username, "698d51a19d8a121ce581499d7b701668", grantedAuthorities);
        }else if (username.equals("dd")) {
            List<GrantedAuthority> grantedAuthorities = new LinkedList<>();
            ((LinkedList<GrantedAuthority>) grantedAuthorities).offer(new SimpleGrantedAuthority("AUTH_PUSH"));
            ((LinkedList<GrantedAuthority>) grantedAuthorities).offer(new SimpleGrantedAuthority("AUTH_POP"));
            return new org.springframework.security.core.userdetails.User(username, "698d51a19d8a121ce581499d7b701668", grantedAuthorities);
        }
        return null;
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

动态给用户增删权限

第一步 在UserDetailsService类的loadUserByUsername里
把用户在数据库里的Role或者Authority全部放到
GrantedAuthorities 里(Role和Authority可以是多个)
第二步 写个增删用户 角色/权限表的 Dao,这样就可以动态的给用户增删权限了。

登录的Remember-Me功能

SpringSecurity是自带这个功能的,默认关闭;
需要打开在configure方法里使用
http.rememberMe();

实现逻辑这这样的:
在登录时选择remember-me会在cookie里设置一个key为rememeber-me的元素,默认过期时间为14天,token为用户名和密码的组合加密生成的。服务端也会把这个token存一份,这样在下次游览器与服务连接时会比照下客户端和服务端的token,有相同的就实现了一定时间免登录了。

一种企业级实现更细粒度权限设计的方案

集团化的企业和连锁性的店面需要定义某个店铺的职工在拥有对应角色权限的同时只对所在店有操作授权,而且要是集团中心员工权限涵盖整个集团。

在基础RBAC模型的基础上再 用户表 里定义一个表示所在店面的字段比如 plant .
可以使用AOP做个before切点做个用户所属店面的判断拦截不具有此店面操作的职工进行操作。

使用SpringSecurity可以很快捷的实现各种权限系统,在基于Role和Authority两种验证授权的方式下灵活应对各种业务需求。

游览器禁用了cookie的解决方法

如果浏览器端拒绝存储cookie信息,就会导致JSESSIONID的值不能正常到达客户端,
那么客户端向服务器发送请求时,就无法将JSESSIONID的值带回到服务器中,从而导致找不到自己的会话Session
我们可以使用URL地址重写的办法解决HttpServletResponse encodeURL()和 encodeRedirectURL() 。
因为cookie在客户端只允许存储4k的数据,那么实际上cookie存储了3k数据就已经到达了一个极限了,cookie就会显得力不从心了,
所以在B/S交互中就需要一个更大的存储数据对象,这就是session存在的重要起因。
如果浏览器不支持Cookie,这些函数将重写您的网址以包含会话信息。根据您使用的Java Web框架,这些函数可能会自动调用(只要使用框架的方法编写URL)。
请注意,在所有情况下都不是所希望的,因为使得会话ID在链接中可见的安全性和缓存影响。 此页面在这个短小的空间中比我可以更好地总结出问题,并提供了禁用此功能的方式。

发布了48 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wangxudongx/article/details/95756587