zoukankan      html  css  js  c++  java
  • springboot整合security实现基于url的权限控制

      权限控制基本上是任何一个web项目都要有的,为此spring为我们提供security模块来实现权限控制,网上找了很多资料,但是提供的demo代码都不能完全满足我的需求,因此自己整理了一版。

      在上代码之前,大家需要理解两个过程:认证和授权

      用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
      访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。

      整合步骤如下:

      1、引入依赖和添加mybatis generator插件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>powerx.io</groupId>
        <artifactId>springboot-security</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>springboot-security</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.5.RELEASE</version>
            <relativePath /> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- 分页插件 -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.2.5</version>
            </dependency>
            <!-- alibaba的druid数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.9</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-maven-plugin</artifactId>
                    <version>1.3.2</version>
                    <configuration>
                        <configurationFile>src/main/resources/generator/generatorConfig.xml</configurationFile>
                        <overwrite>true</overwrite>
                        <verbose>true</verbose>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    
    </project>

      2、建立对应的表,标准的基于角色权限控制的五张表,建表语句我也放到代码中了。

      3、利用逆向工程生成对应的model、mapper和映射文件等

      4、spring security配置,关键位置我都加了注释

      WebSecurityConfig.java

    package com.example.demo.config;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.config.annotation.ObjectPostProcessor;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    
    import com.example.demo.service.UserService;
    
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        UserService userService;
        @Autowired
        MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
        @Autowired
        MyAccessDecisionManager myAccessDecisionManager;
        @Autowired
        AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;
    
        /**
         * 自定义的加密算法
         * @return
         */
        @Bean
        public PasswordEncoder myPasswordEncoder() {
            return new MyPasswordEncoder(); 
        }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService).passwordEncoder(myPasswordEncoder());
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/index.html", "/static/**","/loginPage","/register");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                            o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                            o.setAccessDecisionManager(myAccessDecisionManager);
                            return o;
                        }
                    }).and().formLogin().loginPage("/loginPage").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll().failureHandler(new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    PrintWriter out = httpServletResponse.getWriter();
                    StringBuffer sb = new StringBuffer();
                    sb.append("{"status":"error","msg":"");
                    if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
                        sb.append("用户名或密码输入错误,登录失败!");
                    } else {
                        sb.append("登录失败!");
                    }
                    sb.append(""}");
                    out.write(sb.toString());
                    out.flush();
                    out.close();
                }
            }).successHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    PrintWriter out = httpServletResponse.getWriter();
                    String s = "{"status":"success","msg":"登陆成功"}";
                    out.write(s);
                    out.flush();
                    out.close();
                }
            }).and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler);
        }
    }

      MyFilterInvocationSecurityMetadataSource.java

    package com.example.demo.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.access.SecurityConfig;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.stereotype.Service;
    
    import com.example.demo.dao.PermissionMapper;
    import com.example.demo.model.Permission;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.*;
    import java.util.Map.Entry;
    
    @Service
    public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        @Autowired
        private PermissionMapper permissionMapper;
    
        private HashMap<String, Collection<ConfigAttribute>> map = null;
    
        /**
         * 加载权限表中所有权限
         */
        public void loadResourceDefine() {
            map = new HashMap<String, Collection<ConfigAttribute>>();
    
            List<Permission> permissions = permissionMapper.findAll();
            for (Permission permission : permissions) {
                ConfigAttribute cfg = new SecurityConfig(permission.getPermissionname());
                List<ConfigAttribute> list = new ArrayList<>();
                list.add(cfg);
                map.put(permission.getUrl(), list);
            }
    
        }
    
        /**
         * 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法, 用来判定用户
         * 是否有此权限。如果不在权限表中则放行。
         */
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            if (map == null) {
                loadResourceDefine();
            }
            // object 中包含用户请求的request的信息
            HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
            for (Entry<String, Collection<ConfigAttribute>> entry : map.entrySet()) {
                String url = entry.getKey();
                if (new AntPathRequestMatcher(url).matches(request)) {
                    return map.get(url);
                }
            }
            return null;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }

      MyAccessDecisionManager.java

    package com.example.demo.config;
    
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.stereotype.Service;
    
    import java.util.Collection;
    import java.util.Iterator;
    
    
    @Service
    public class MyAccessDecisionManager implements AccessDecisionManager {
    
        /**
         * decide 方法是判定是否拥有权限的决策方法,authentication是CustomUserService
         * 中循环添加到 GrantedAuthority 对象中的权限信息集合,object 包含客户端发起的请求的requset信息,
         * 可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
         * configAttributes为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)
         * 这个方法返回的结果.
         * 
         */
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
    
            if(null== configAttributes || configAttributes.size() <=0) {
                return;
            }
            ConfigAttribute c;
            String needRole;
            for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
                c = iter.next();
                needRole = c.getAttribute();
                for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
                    if(needRole.trim().equals(ga.getAuthority())) {
                        return;
                    }
                }
            }
            throw new AccessDeniedException("no right");
        }
    
        @Override
        public boolean supports(ConfigAttribute attribute) {
            return true;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }

      AuthenticationAccessDeniedHandler.java

    package com.example.demo.config;
    
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @Component
    public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
            resp.setContentType("application/json;charset=UTF-8");
            PrintWriter out = resp.getWriter();
            out.write("{"status":"error","msg":"权限不足,请联系管理员!"}");
            out.flush();
            out.close();
        }
    }

      MyPasswordEncoder.java

    package com.example.demo.config;
    
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    public class MyPasswordEncoder implements PasswordEncoder {
        @Override
        public String encode(CharSequence charSequence) {
            return charSequence.toString();
        }
    
        @Override
        public boolean matches(CharSequence charSequence, String s) {
            return s.equals(charSequence.toString());
        }
    }

      UserServiceImpl.java

    package com.example.demo.service.impl;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.example.demo.dao.PermissionMapper;
    import com.example.demo.dao.RoleMapper;
    import com.example.demo.dao.UserMapper;
    import com.example.demo.model.Permission;
    import com.example.demo.model.User;
    import com.example.demo.service.UserService;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private PermissionMapper permissionMapper;
        @Autowired
        private RoleMapper roleMapper;
        @Autowired
        private UserMapper userMapper;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userMapper.selectByUsername(username);
            if (user != null) {
                List<Permission> permissions = permissionMapper.findByUserId(user.getId());
                List<GrantedAuthority> grantedAuthorities = new ArrayList <>();
                for (Permission permission : permissions) {
                    if (permission != null && permission.getPermissionname()!=null) {
    
                    GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getPermissionname());
                    grantedAuthorities.add(grantedAuthority);
                    }
                }
                return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
            } else {
                throw new UsernameNotFoundException("username: " + username + " do not exist!");
            } 
        }
    
        @Transactional
        @Override
        public void userRegister(String username, String password) {
            User user  = new User();
            user.setUsername(passwordEncoder.encode(username));
            user.setPassword(password);
            userMapper.insert(user);
            User rtnUser =userMapper.selectByUsername(username);
            //注册成功默认给用户的角色是user
            roleMapper.insertUserRole(rtnUser.getId(), 2);
        }
    
    }

      至此,整合基本完毕,其它控制层的代码和mapper层的代码不再贴出,需要注意的是注册用户的时候我们要用自定义的加密工具对密码进行加密(当然在demo中我什么也没做),其它的一些功能比如给用户加角色、给角色加权限等的增删改查,大家可以根据需要自行添加,另外在permissionMapper.findByUserId(user.getId())这里我写了一个五张表的关联查询,可以根据userid可以查出用户所有对应的权限。

      为了方便大家和自己以后参考,代码已上传至码云:https://gitee.com/hehang_com/springboot-security

  • 相关阅读:
    TP5.1:request请求对象(使用四种方式获取)
    TP5.1:facade静态代理
    php:定义时间跳转到指定页面
    TP5.1:依赖注入、绑定一个类到容器里、绑定一个闭包到容器中
    sql:表中数据全部删除之后,重新插入时,从1开始增加
    通过CSS3实现:鼠标悬停图片360度旋转效果
    TP5.1 配置的获取与设置
    详细讲解:通过composer安装TP5.1(Thinkphp5.1)
    TP5.0: 显示错误信息
    socket2
  • 原文地址:https://www.cnblogs.com/hhhshct/p/9726378.html
Copyright © 2011-2022 走看看