• security


    Security

    spring提供的一套安全机制,SpringSecurity注重于为Java应用提供认证和授权功能,像所有的Spring项目一样,它对自定义需求具有强大的扩展性

    jwt

    在security之前,先讲解一下jwt,jwt是Javaweb应用中的令牌,具体请看之前的jwt,作用是提供用户识别的机制

    授权原理

    • 用户调用登录接口,登录成功后获取到JWT的token;
    • 之后用户每次调用接口都在http的header中添加一个叫Authorization的头,值为JWT的token;
    • 后台程序通过对Authorization头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。

    权限的实现

    • ums_admin:后台用户表
    • ums_role:后台用户角色表
    • ums_permission:后台用户权限表
    • ums_admin_role_relation:后台用户和角色关系表,用户与角色是多对多关系
    • ums_role_permission_relation:后台用户角色和权限关系表,角色与权限是多对多关系
    • ums_admin_permission_relation:后台用户和权限关系表(除角色中定义的权限以外的加减权限),加权限是指用户比角色多出的权限,减权限是指用户比角色少的权限

    具体实现

    配置

    <!--SpringSecurity依赖配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--Hutool Java工具包-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.5.7</version>
    </dependency>
    <!--JWT(Json Web Token)登录支持-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
    

    jwt的工具类

    security

    /**
     * SpringSecurity的配置
     * Created by macro on 2018/4/26.
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled=true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private UmsAdminService adminService;
        @Autowired
        private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
        @Autowired
        private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
                    .disable()
                    .sessionManagement()// 基于token,所以不需要session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问
                            "/",
                            "/*.html",
                            "/favicon.ico",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/swagger-resources/**",
                            "/v2/api-docs/**"
                    )
                    .permitAll()
                    .antMatchers("/admin/login", "/admin/register")// 对登录注册要允许匿名访问
                    .permitAll()
                    .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                    .permitAll()
    //                .antMatchers("/**")//测试时全部运行访问
    //                .permitAll()
                    .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                    .authenticated();
            // 禁用缓存
            httpSecurity.headers().cacheControl();
            // 添加JWT filter
            httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
            //添加自定义未授权和未登录结果返回
            httpSecurity.exceptionHandling()
                    .accessDeniedHandler(restfulAccessDeniedHandler)
                    .authenticationEntryPoint(restAuthenticationEntryPoint);
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService())
                    .passwordEncoder(passwordEncoder());
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public UserDetailsService userDetailsService() {
            //获取登录用户信息
            return username -> {
                UmsAdmin admin = adminService.getAdminByUsername(username);
                if (admin != null) {
                    List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId());
                    return new AdminUserDetails(admin,permissionList);
                }
                throw new UsernameNotFoundException("用户名或密码错误");
            };
        }
    
        @Bean
        public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
            return new JwtAuthenticationTokenFilter();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
    }
    
    • configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
    • configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
    • RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;
    • RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;
    • UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
    • UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;
    • PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;
    • JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。

    security的配置类

    package springboot.boot.component;
    
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    import org.springframework.web.filter.OncePerRequestFilter;
    import springboot.boot.common.utils.JwtTokenUtil;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * JWT登录授权过滤器
     * Created by macro on 2018/4/26.
     */
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
        private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
        @Autowired
        private UserDetailsService userDetailsService;
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
        @Value("${jwt.tokenHeader}")
        private String tokenHeader;
        @Value("${jwt.tokenHead}")
        private String tokenHead;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain chain) throws ServletException, IOException {
            String authHeader = request.getHeader(this.tokenHeader);
            if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
                String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
                String username = jwtTokenUtil.getUserNameFromToken(authToken);
                LOGGER.info("checking username:{}", username);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        LOGGER.info("authenticated user:{}", username);
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
            chain.doFilter(request, response);
        }
    }
    
    
    package springboot.boot.component;
    
    import cn.hutool.json.JSONUtil;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    import springboot.boot.common.api.CommonResult;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 当未登录或者token失效访问接口时,自定义的返回结果
     * Created by macro on 2018/5/14.
     */
    @Component
    public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
            response.getWriter().flush();
        }
    }
    
    
    package springboot.boot.component;
    
    import cn.hutool.json.JSONUtil;
    
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    import springboot.boot.common.api.CommonResult;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 当访问接口没有权限时,自定义的返回结果
     * Created by macro on 2018/4/26.
     */
    @Component
    public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
        @Override
        public void handle(HttpServletRequest request,
                           HttpServletResponse response,
                           AccessDeniedException e) throws IOException, ServletException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
            response.getWriter().flush();
        }
    }
    

    security的权限类

    package springboot.boot.domain;
    
    import lombok.Builder;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * Created by macro on 2020/10/15.
     */
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Builder
    public class AdminUserDetails implements UserDetails {
        private String username;
        private String password;
        private List<String> authorityList;
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return this.authorityList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        }
    
        @Override
        public String getPassword() {
            return this.password;
        }
    
        @Override
        public String getUsername() {
            return this.username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    

    感谢大佬,macro

  • 相关阅读:
    2 安装部署flume
    1 flume快速入门——十分钟学会flume
    3、剑指offer-数组——数组中重复的数字
    3.kafka安装配置
    2、剑指offer-字符串——替换空格
    1、剑指offer-数组——二维数组中的查找
    JVM虚拟机垃圾回收(GC)算法及优缺点
    Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/collect/ImmutableMap
    LeetCode 617. 合并二叉树 Java
    Linux命令大全
  • 原文地址:https://www.cnblogs.com/xiaozhazhahui/p/15078286.html
Copyright © 2020-2023  润新知