Nginx+Springboot+Security+CAS 整合方案-XML 实现SSO客户端

javaconfig版本: https://www.cnblogs.com/question-sky/p/7068511.html

以下使用的是SpringBoot 2.1.1进行测试

0 Maven引用

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- 添加spring security cas支持 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-cas</artifactId>
        </dependency>

1 Springboot 启动类注解和配置扫描

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  //  启用方法级别的权限认证
@ImportResource(locations={"classpath:spring/spring-security.xml"})
server.servlet.context-path=/b  //设置项目的全局URL前缀(此处只是项目需求,与整合无关)

2 配置spring-security.xml

  localhost:8080 为CAS认证服务器(认证服务器搭建 https://blog.csdn.net/shanchahua123456/article/details/85547516

  localhost:8787 为当前项目

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <!-- http标签是 spring security 配置标签-->
    <http pattern="/static/**" security="none"></http>
    <http pattern="/css/**" security="none"></http>
    <http pattern="/img/**" security="none"></http>
    <http pattern="/js/**" security="none"></http>
    <http pattern="/plugins/**" security="none"></http>

    <!-- security 配置  use-expressions:是否启动SPEL表达式 默认是true -->
    <!-- cas入口点引用  entry-point-ref是security框架接入第三方服务的通用入口     -->
    <http use-expressions="true" entry-point-ref="casProcessingFilterEntryPoint">
        <!-- security 配置 页面的拦截规则-->
        <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
        <intercept-url pattern="/b/qq/**" access="hasRole('ROLE_USER') and hasIpAddress('10.10.10.3')" />
        <csrf disabled="true"/>
        <!-- html允许加载 同源iframe -->
        <headers>
            <frame-options policy="SAMEORIGIN"/>
        </headers>
        <!-- custom-filter为过滤器, position 表示将过滤器放在指定的位置上,before表示放在指定位置之前  ,after表示放在指定的位置之后  -->
        <!-- 将CAS的过滤器 加入security框架-->
        <custom-filter ref="casAuthenticationFilter"  position="CAS_FILTER" />
        <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
        <custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
    </http>

    <!-- CAS入口点 开始 -->
    <!--  localhost:8080 为CAS认证服务器地址-->
    <!--  localhost:8787 为本项目地址-->
     <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
         <!-- 单点登录CAS服务器登录URL , localhost:8080 为CAS认证服务器,需要登录时会重定向到此URL上-->
        <beans:property name="loginUrl" value="http://localhost:8080/cas/login"/>
        <beans:property name="serviceProperties" ref="serviceProperties"/>
    </beans:bean>
    <!--设置客户端service的属性-->
    <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
        <!--设置回调的service路径 配置 自身工程的根地址+/login/cas 是固定写法。必须是此格式,不然会造成死循环,出现“重定向过多”错误-->
        <!--注意:因为本项目设置了公共URL前缀server.servlet.context-path=/b,所以根地址为localhost:8787/b,否则根地址是localhost:8787  -->
        <beans:property name="service" value="http://localhost:8787/b/login/cas"/>
</beans:bean>
<!-- CAS入口点 结束 -->


    <!-- 认证过滤器 开始 -->
    <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager"/>
    </beans:bean>
    <!-- 认证管理器 -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider  ref="casAuthenticationProvider">
        </authentication-provider>
    </authentication-manager>
    <!-- 认证提供者 -->
    <beans:bean id="casAuthenticationProvider"     class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
        <beans:property name="authenticationUserDetailsService">
            <beans:bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
                <beans:constructor-arg ref="userDetailsService" />
            </beans:bean>
        </beans:property>
        <!--serviceProperties属性主要应用于ticketValidator用于去cas服务端检验ticket-->
        <beans:property name="serviceProperties" ref="serviceProperties"/>
        <!-- ticketValidator 为票据验证器 -->
        <beans:property name="ticketValidator">
            <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <!--localhost:8080 为CAS服务器 -->
                <beans:constructor-arg index="0" value="http://localhost:8080/cas"/>
            </beans:bean>
        </beans:property>
        <beans:property name="key" value="an_id_for_this_auth_provider_only"/>
    </beans:bean>
    <!-- 自定义授权类  其实现SpringSecurity的UserDetailsService接口,但是只负责授权。密码认证交给CAS服务器完成-->
    <!-- 其在CAS认证之后 再执行授权  -->
    <beans:bean id="userDetailsService" class="com.example.nginxtest.UserDetailServiceImpl"/>

    <!-- 认证过滤器 结束 -->


    <!-- 单点登出  开始  -->
    <beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter">
          <!--CAS 3.5 以上版本 需要配以下属性 -->
          <!--设置cas服务端路径前缀,应用于front channel的注销请求-->
          <!--casServerUrlPrefix  与 cas服务器的配置文件相同\WEB-INF\cas.properties-->
          <beans:property name="casServerUrlPrefix" value="https://localhost:8080/cas"></beans:property>
          <beans:property name="ignoreInitConfiguration" value="true"></beans:property>
    </beans:bean>

    <!-- 经过以下配置,当用户在地址栏输入 本工程+/logout/cas即可登出   -->
    <beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <!--CAS服务器登出URL   service= 为登出后跳转URL-->
        <beans:constructor-arg value="http://localhost:8080/cas/logout?service=http://www.baidu.com"/>
        <beans:constructor-arg>
            <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
        </beans:constructor-arg>
        <!--配置本地工程登出地址  /logout/cas为自定义登出路径,cas框架自动与requestSingleLogoutFilter中的地址绑定-->
        <beans:property name="filterProcessesUrl" value="/logout/cas"/>
    </beans:bean>
    <!-- 单点登出  结束 -->

</beans:beans>

4 Security角色权限认证。此处只为测试简单,真实环境需要注入DAO层查询权限。

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.List;

public class UserDetailServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("经过认证类:"+username);

        List<GrantedAuthority> authorities=new ArrayList();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        return new User(username,"",authorities);
    }

}

5 NGINX配置

经测试使用浏览器通过NGINX访问两个配置了CAS登录的不同服务,可以实现SSO。


        location /b {
           proxy_pass  http://192.168.1.55:8787; 
 
           proxy_set_header X-Real-IP $remote_addr; 
           proxy_set_header Host $host;   
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header Cookie $http_cookie;
        }

        location /a {
           proxy_pass  http://192.168.1.55:8788; 
 
           proxy_set_header X-Real-IP $remote_addr; 
           proxy_set_header Host $host;   
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header Cookie $http_cookie;
        }

7 Security框架取得用户信息

  方案一:SecurityContextHolder中获取

  可以自定义实现UserDetails类

//获得当前登陆用户对应的对象
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
    .getAuthentication()
    .getPrincipal();

//获得当前登陆用户所拥有的所有权限
GrantedAuthority[] authorities = userDetails.getAuthorities();

方案二:session中获取

SecurityContextImpl securityContextImpl = (SecurityContextImpl) request
.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
// 登录名
securityContextImpl.getAuthentication().getName();
// 登录密码,未加密的
securityContextImpl.getAuthentication().getCredentials();

WebAuthenticationDetails details = (WebAuthenticationDetails) securityContextImpl
.getAuthentication().getDetails();
// 获得访问地址
details.getRemoteAddress();
// 获得sessionid
details.getSessionId();
// 获得当前用户所拥有的权限
List<GrantedAuthority> authorities = (List<GrantedAuthority>) securityContextImpl
.getAuthentication().getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
System.out.println("Authority" + grantedAuthority.getAuthority());
}

8 CSRF+AJAX跨站伪造

上边配置是关闭CSRF。若开启的话,结合AJAX需要注意。

http://www.cnblogs.com/Joeee/p/7544573.html

https://www.cnblogs.com/zhufu9426/p/7814084.html

https://blog.csdn.net/starrrr2/article/details/50074445

html设置:全局index页面设置即可, 不需要每个页面都写

<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />

ajax 请求:

var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");

 $.ajax({
          url: "./deleteRes",
          method: 'post',
          dataType: 'json',
          data: {
            resIds: str,
          },
          beforeSend: function(request) {
            request.setRequestHeader(header, token);
          }
,
          success: function(resp) {
           }
     });
 

猜你喜欢

转载自blog.csdn.net/shanchahua123456/article/details/85570647