• cas单点登录原理与实现(整合springsecurity)


    一、cas原理分析

    SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。CAS是一种基于http协议的B/S应用系统单点登录实现方案,认识CAS之前首先要熟悉http协议、Session与Cookie等Web开发基本知识。

    1.1 Cas登录:
    两次前端跳转、一次后端验证

    1.1.1 首次访问应用A

    第一次跳转:

    客户端访问应用系统A,应用系统判断Session发现未登录,返回302跳转到sso登录页面,并传递service参数给sso,该service参数有两个作用:(回跳、认证)
    1、 service一般传递应用系统url地址,用于sso认证通过后回跳到应用系统A;
    2、service参数同时会被cas服务端的作为cas客户端的唯一标记记录下来,用于后期匹配相应的认证凭据;

    第二次跳转:

    浏览器显示登录页面,用户输入账号密码登录成功后,sso会返回302跳转回到原来请求的应用系统页面,并携带ticket参数,作为认证票据,同时通过Set-Cookie向浏览器记录TGT,(TGT的作用将在下一个应用系统需要登录的时候体现出作用,是避免重复登录的关键)

    后台进行一次票据验证:

    应用系统接收到带有ticket的请求后,从后台直接向sso服务器发起一个http请求,将service和ticket作为参数,用于验证ticket的有效性;如果ticket有效,sso服务器将返回该ticket对应的登录用户名。

    图例:

    在这里插入图片描述
    1.1.2 访问A登陆后首次访问应用B

    访问应用B系统,根据session判断未登录,重定向到CAS Serve,根据Cookie里面的TGT找到对应的用户信息,携带ticket重定向会应用系统B,应用系统B接收到带有ticket的请求后,从后台直接向sso服务器发起一个http请求,将service和ticket作为参数,用于验证ticket的有效性;如果ticket有效,sso服务器将返回该ticket对应的登录用户名

    二、实现cas客户端

    yaml配置:

    security:
      cas:
        server:
          host: https://localhost:8443/cas  #cas服务器的地址
          login: ${security.cas.server.host}/login  #单点登录地址
          logout: ${security.cas.server.host}/logout  #单点登出店址
        service:
          webHost: http://localhost:8090  #应用系统的地址
          login: /login       #应用系统的登录入口
          logout: /logout     #应用系统的登出入口
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.1 参数配置类:

    • CAS认证中心参数配置类
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    
    @Data
    @Configuration
    public class CasServerConfig {
    
      @Value("${security.cas.server.host}")
      private String host;
    
      @Value("${security.cas.server.login}")
      private String login;
    
      @Value("${security.cas.server.logout}")
      private String logout;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 应用服务器参数配置类
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    
    @Data
    @Configuration
    public class CasServiceConfig {
    
      @Value("${security.cas.service.webHost}")
      private String webHost;
    
      @Value("${security.cas.service.login}")
      private String login;
    
      @Value("${security.cas.service.logout}")
      private String logout;
    
      private Boolean sendRenew = false;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.2 CAS配置

    • CAS配置类
    import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.cas.ServiceProperties;
    import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
    import org.springframework.security.cas.authentication.CasAuthenticationProvider;
    import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
    import org.springframework.security.cas.web.CasAuthenticationFilter;
    import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
    import org.springframework.security.web.authentication.logout.LogoutFilter;
    
    @Configuration
    public class SecurityConfiguration {
    
      @Autowired
      private CasServerConfig casServerConfig;
    
      @Autowired private CasServiceConfig casServiceConfig;
    
      @Bean
      public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(
        this.casServiceConfig.getWebHost() + this.casServiceConfig.getLogin()); //回跳地址
        serviceProperties.setSendRenew(this.casServiceConfig.getSendRenew());//是否敏感,即登录不同的应用系统是否还要重新登录
       
        serviceProperties.setAuthenticateAllArtifacts(true); //是否对没有ticket的访问需要验证
        return serviceProperties;
      }
    
      /***
       * CAS认证过滤器
       * @param authenticationManager
       * @param serviceProperties
       * @return
       */
      @Bean
      public CasAuthenticationFilter casAuthenticationFilter(
              AuthenticationManager authenticationManager, ServiceProperties serviceProperties) {
        CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
        casAuthenticationFilter.setAuthenticationManager(authenticationManager);
        casAuthenticationFilter.setServiceProperties(serviceProperties);
        casAuthenticationFilter.setFilterProcessesUrl(
            this.casServiceConfig.getLogin());
        casAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false);
        casAuthenticationFilter.setAuthenticationSuccessHandler(
            new UrlAuthenticationSuccessHandler("/"));
        return casAuthenticationFilter;
      }
    
    
    /**
       * CAS入口
       * @param serviceProperties
       * @return
       */
      @Bean
      public CasAuthenticationEntryPoint casAuthenticationEntryPoint(
          ServiceProperties serviceProperties) {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl(this.casServerConfig.getLogin());
        entryPoint.setServiceProperties(serviceProperties);
        return entryPoint;
      }
    
    
      /**
       * CASticket验证
       * @return
       */
      @Bean
      public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
        return new Cas20ServiceTicketValidator(this.casServerConfig.getHost());
      }
    
      @Bean
      public CasAuthenticationProvider casAuthenticationProvider(
          AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService,
          ServiceProperties serviceProperties,
          Cas20ServiceTicketValidator ticketValidator) {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setKey("casProvider");
        provider.setServiceProperties(serviceProperties);
        provider.setTicketValidator(ticketValidator);
        provider.setAuthenticationUserDetailsService(userDetailsService);//自己的userDetailService
    
        return provider;
      }
    
     /**
       * 登出过滤器
       * @return
       */
      @Bean
      public LogoutFilter logoutFilter() {
        String logoutRedirectPath =
            this.casServerConfig.getLogout() + "?service=" + this.casServiceConfig.getWebHost();
        LogoutFilter logoutFilter =
            new LogoutFilter(logoutRedirectPath, new CasSecurityContextLogoutHandler());
        logoutFilter.setFilterProcessesUrl(this.casServiceConfig.getLogout());
        return logoutFilter;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 认证成功处理类
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
    import org.springframework.util.StringUtils;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.util.Collection;
    
    @Slf4j(topic = "c.successHandler")
    public class UrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    
        public UrlAuthenticationSuccessHandler() {
            super();
        }
    
    
        public UrlAuthenticationSuccessHandler(String defaultTargetUrl) {
            super(defaultTargetUrl);
        }
    
        /**
         * 认证成功后
         * @param request
         * @param response
         * @param authentication
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    
    
            super.onAuthenticationSuccess(request, response, authentication);
        }
    
        /**
         *返回一个认证成功后的路径
         * @param request
         * @param response
         * @return
         */
    
        @Override
        protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
            StringBuffer targetUrl = null;
            //根据自己需求写
            HttpSession session = request.getSession();
            targetUrl = (StringBuffer) session.getAttribute("REQUEST_URL");
            log.debug("目标路径为{}", targetUrl);
            return targetUrl.toString();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 自定义过滤器(存储要访问的路径)
    mport com.hzx.hzxy_grid.domain.SecurityUser;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.util.Collection;
    
    @Component
    @Slf4j
    public class HttpParamsFilter implements Filter {
    
    
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {}
    
      @Override
      public void doFilter(
              ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
          throws IOException, ServletException {
          final HttpServletRequest request = (HttpServletRequest) servletRequest;
          final HttpServletResponse response = (HttpServletResponse) servletResponse;
          HttpSession session = request.getSession();
          StringBuffer requestURL = request.getRequestURL();
          log.info("请求地址:" + requestURL);
    
        chain.doFilter(request, response);
      }
    
      @Override
      public void destroy() {}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 单点登出过滤器
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.logout.LogoutHandler;
    import org.springframework.util.Assert;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    public class CasSecurityContextLogoutHandler implements LogoutHandler {
      protected final Log logger = LogFactory.getLog(this.getClass());
      private boolean invalidateHttpSession = true;
      private boolean clearAuthentication = true;
    
      public CasSecurityContextLogoutHandler() {
      }
    
      public void logout(
              HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        Assert.notNull(request, "HttpServletRequest required");
        if (this.invalidateHttpSession) {
          HttpSession session = request.getSession(false);
          if (session != null) {
            this.logger.debug("Invalidating session: " + session.getId());
            //发布退出登录事件,自己增加,监听此事件处理一些事情
            session.invalidate(); //session失效
    //        LogoutEvent event = new LogoutEvent();
    //        event.setSessionId(session.getId());
    //        EventPublisherUtil.publish(event);
          }
        }
    
        if (this.clearAuthentication) {
          SecurityContext context = SecurityContextHolder.getContext();
          context.setAuthentication((Authentication)null);
        }
    
        SecurityContextHolder.clearContext();
      }
    
      public boolean isInvalidateHttpSession() {
        return this.invalidateHttpSession;
      }
    
      public void setInvalidateHttpSession(boolean invalidateHttpSession) {
        this.invalidateHttpSession = invalidateHttpSession;
      }
    
      public void setClearAuthentication(boolean clearAuthentication) {
        this.clearAuthentication = clearAuthentication;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • springsecurity配置类
    import com.hzx.hzxy_grid.config.custom.CustomFilter;
    import com.hzx.hzxy_grid.config.custom.CustomUrlDecisionManager;
    import com.hzx.hzxy_grid.handler.CustomizeAccessDeniedHandler;
    import org.jasig.cas.client.session.SingleSignOutFilter;
    import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.security.SecurityProperties;
    import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.cas.authentication.CasAuthenticationProvider;
    import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
    import org.springframework.security.cas.web.CasAuthenticationFilter;
    import org.springframework.security.config.BeanIds;
    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.web.access.intercept.FilterSecurityInterceptor;
    import org.springframework.security.web.authentication.logout.LogoutFilter;
    import org.springframework.web.cors.CorsUtils;
    
    @Configuration
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    public class CasWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private CasAuthenticationEntryPoint casAuthenticationEntryPoint;
    
        @Autowired
        private CasAuthenticationProvider casAuthenticationProvider;
    
        @Autowired
        private CasAuthenticationFilter casAuthenticationFilter;
    
        @Autowired
        private LogoutFilter logoutFilter;
    
        @Autowired
        private CasServerConfig casServerConfig;
    
        @Autowired
        private HttpParamsFilter httpParamsFilter;
    
        @Autowired
        private CustomFilter customFilter;
    
        @Autowired
        private CustomUrlDecisionManager customUrlDecisionManager;
    
    
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers(
                    "/swagger-resources/**",
                    "/swagger-ui.html",
                    "/v2/api-docs",
                    "/webjars/**",
                    "/v3/api-docs",
    //                "/login",
                    "/static/**",
                    "/api/**"
    
            );
    
            super.configure(web);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.headers().frameOptions().disable();
    
            http.csrf().disable();
            http.authorizeRequests()
                    .requestMatchers(CorsUtils::isPreFlightRequest)
                    .permitAll()
                    .anyRequest()
                    .authenticated(); // 所有资源都需要登陆后才可以访问。
    
            http.logout().permitAll(); // 不拦截注销
    
            http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint)
                    .accessDeniedHandler(new CustomizeAccessDeniedHandler())
            ;
            // 单点注销的过滤器,必须配置在SpringSecurity的过滤器链中,如果直接配置在Web容器中,貌似是不起作用的。我自己的是不起作用的。
            SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
            singleSignOutFilter.setArtifactParameterName(this.casServerConfig.getHost());
    
            http.addFilter(casAuthenticationFilter)
                    .addFilterBefore(logoutFilter, LogoutFilter.class)
                    .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class);
            http.addFilterBefore(httpParamsFilter, FilterSecurityInterceptor.class);
            http.addFilterBefore(filterSecurityInterceptor(), FilterSecurityInterceptor.class);
            http.antMatcher("/**");
        }
    
        /**
         * 权限拦截
         *
         * @return
         * @throws Exception
         */
        public FilterSecurityInterceptor filterSecurityInterceptor() throws Exception {
            FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
            filterSecurityInterceptor.setSecurityMetadataSource(customFilter);
            filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
            filterSecurityInterceptor.setAccessDecisionManager(customUrlDecisionManager);
            return filterSecurityInterceptor;
        }
    
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(casAuthenticationProvider);
        }
    
        @Bean
        public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener>
        singleSignOutHttpSessionListener() {
            ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> servletListenerRegistrationBean =
                    new ServletListenerRegistrationBean<>();
            servletListenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());
            return servletListenerRegistrationBean;
        }
    
    
        /**
         * AuthenticationManager
         *
         * @return
         * @throws Exception
         */
        @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • CustomFilter类
    /**
     * 根据访问的路径判断出所需的角色
     */
    
    @Component
    @Slf4j(topic = "c.CustomUrl")
    public class CustomFilter implements FilterInvocationSecurityMetadataSource {
    
        //路径匹配
        private AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Autowired
        private MenuService menuService;
    
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
            // 获取请求的Url
            String requestUrl = ((FilterInvocation) o).getRequestUrl();
            List<Menu> menuWithRoles = menuService.getMenuWithRoles();
            for (Menu menu : menuWithRoles) {
                //访问的路径是否跟菜单的里的路径相同
                if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
    
                    String[] roles = menu.getRoleList().stream().map(Role::getRoleName).toArray(String[]::new);
                    log.info("需要的权限为{}", roles);
                    System.out.println(roles);
                    return SecurityConfig.createList(roles);
                }
            }
    
    
            return SecurityConfig.createList("ROLE_LOGIN");
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return false;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • CustomDecisionManager
    @Component
    public class CustomUrlDecisionManager implements AccessDecisionManager {
        @Override
        public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
            for (ConfigAttribute configAttribute : collection) {
                String needRole = configAttribute.getAttribute();
                if ("ROLE_LOGIN".equals(needRole)) {
                    if (authentication instanceof AnonymousAuthenticationToken) {
                        throw new AccessDeniedException("用户未登录,请登录");
                    } else
                        return;
                }
    
                //判断是否有对应的权限信息
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                for (GrantedAuthority authority : authorities) {
                    if(authority.getAuthority().equals(needRole))
                        return;
                }
            }
            throw new AccessDeniedException("用户无权限访问");
        }
    
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return false;
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return false;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 未授权无法访问CustomizeAccessDeniedHandler
    import com.hzx.hzxy_grid.domain.R;
    import com.hzx.hzxy_grid.enums.Status;
    import com.hzx.hzxy_grid.util.ResponseUtil;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 未授权,用户无权访问
     */
    
    public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
            ResponseUtil.out(httpServletResponse,R.error().status(Status.UNAUTHORIZED));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • AuthenticationUserDetailsService
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.hzx.hzxy_grid.domain.Admin;
    import com.hzx.hzxy_grid.domain.Role;
    import com.hzx.hzxy_grid.domain.SecurityUser;
    import com.hzx.hzxy_grid.mapper.AdminMapper;
    import com.hzx.hzxy_grid.mapper.RoleMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
    import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    
    @Slf4j(topic = "c.UserDetailServiceImpl")
    @Service
    public class MyUserDetailServiceImpl implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
    
        @Autowired
        private RoleMapper roleMapper;
    
        @Autowired
        private AdminMapper adminMapper;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Override
        public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
    
            String username = token.getName();
            Admin admin = adminMapper.selectOne(new LambdaQueryWrapper<Admin>().eq(Admin::getUsername, username));
            log.info("admin:{}", admin);
            if (admin == null)
                throw new UsernameNotFoundException("用户名不存在");
            SecurityUser securityUser = new SecurityUser();
    
    
            List<String> permissionList = null;
    
            //如果redis不存在该用户的权限
            if (!redisTemplate.hasKey("permission_" + admin.getUid())) {
                //添加权限
                List<Role> roleList = roleMapper.getRolesByAdminId(admin.getUid());
                permissionList = roleList.stream().map(role -> role.getRoleName()).collect(Collectors.toList());
                securityUser.setPermissionValueList(permissionList);
                securityUser.setCurrentUserInfo(admin);
                log.info("从数据库中查询到用户的权限为{}", permissionList);
            } else {
    
                permissionList = (List<String>) redisTemplate.opsForValue().get("permission_" + admin.getUid());
                log.info("从redis中查询到用户的权限为{}",permissionList);
            }
    
            securityUser.setPermissionValueList(permissionList);
            securityUser.setCurrentUserInfo(admin);
            log.info("用户的权限为{}", permissionList);
            return securityUser;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • SecurityUser
    import lombok.Data;
    import org.springframework.security.core.CredentialsContainer;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.util.StringUtils;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    @Data
    public class SecurityUser implements UserDetails, CredentialsContainer {
    
        //当前登录用户
        private transient Admin currentUserInfo;
    
        //当前权限
        private List<String> permissionValueList;
    
        public SecurityUser() {
        }
    
        public SecurityUser(Admin admin) {
            if (admin != null) {
                this.currentUserInfo = admin;
            }
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }
    
            return authorities;
        }
    
        @Override
        public String getPassword() {
            return currentUserInfo.getPassword();
        }
    
        @Override
        public String getUsername() {
            return currentUserInfo.getUsername();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    
        @Override
        public void eraseCredentials() {
            
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
  • 相关阅读:
    Spring框架
    vue3-响应式函数
    任意文件下载(读取)
    代码随想录第14天 | ● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组
    Redis之淘汰策略
    今日睡眠质量记录76
    公开的mqtt服务器如何获得等问题
    外包干了6天后悔了,技术明显进步。。。。。
    【物理应用】基于Matlab实现LBM-D2Q9模型粗糙界面流动
    一个实例掌握java的stream(扑克发牌,洗牌,牌型判断)
  • 原文地址:https://blog.csdn.net/qq_46624276/article/details/125456378