spring-security+spring-session配置

spring-session的配置

1.dependency
2.applicationContext.xml
3.web.xml
4.分布式
5.遇到的一些问题

1. dependency

spring-session提供了一个集成的jar包,只需要导入这一个就可以了.

<!-- spring session -->
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
      <version>1.2.1.RELEASE</version>
    </dependency>
<!-- ./spring session -->

该pom配置对应的jar包是空的,会导入以下一些依赖,你也可以直接引用以下依赖

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.1.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.2.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
<scope>compile</scope>
</dependency>

有这些依赖就可以安心进行下一步了

2. applicationContext.xml

首先贴出applicationContext-security.xml的代码

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:s="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--    <s:http security="none" pattern="/login"/>-->
    <s:http  entry-point-ref="restAuthenticationEntryPoint"    >
        <s:csrf disabled="true"/>
        <s:form-login authentication-success-handler-ref="mySuccessHandler" authentication-failure-handler-ref="myFailureHandler"  default-target-url="/t3.html" />
        <s:logout delete-cookies="JSESSIONID"/>
    <!--    <s:session-management invalid-session-url="/t1.html" >
            &lt;!&ndash; 第二个人登录,第一个人的认证会失效 &ndash;&gt;
            <s:concurrency-control max-sessions="1" />
            &lt;!&ndash; 第一个人登录后,第二个人无法用相同账户登录 &ndash;&gt;
            &lt;!&ndash;<s:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>&ndash;&gt;
        </s:session-management>-->
        <s:intercept-url pattern="/login*"   access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <s:intercept-url pattern="/secure/**"  access="permitAll"/>
        <s:intercept-url pattern="/admin/**" access="hasRole('admin')"  />
        <s:intercept-url pattern="/**" access="authenticated"/>
    </s:http>

    <bean id="mySuccessHandler" class="com.xx.xx.security.RestAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/index.html"/>
    </bean>
    <bean id="myFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>
    <!-- Simple namespace-based configuration -->
    <s:authentication-manager>
        <s:authentication-provider ref='secondLdapProvider' />
    </s:authentication-manager>

    <!-- This bean points at the embedded directory server created by the ldap-server element above  -->
    <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <constructor-arg value="ldap://127.0.0.1:389/dc=xxx,dc=com"/>
        <property name="userDn" value="xxx"></property>
        <property name="password" value="xxx"></property>
    </bean>

    <bean id="customJdbcUserDetailsService" class="com.xx.xx.security.CustomJdbcUserDetailsService">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="customAuthoritiesPopulator" class="com.xx.xx.security.CustomAuthoritiesPopulator">
        <constructor-arg ref="customJdbcUserDetailsService"></constructor-arg>
    </bean>
    <bean id="secondLdapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
                <constructor-arg ref="contextSource" />
                <property name="userSearch">
                    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
                      <constructor-arg index="0" value="OU=xxx"/>
                      <constructor-arg index="1" value="(sAMAccountName={0})"/>
                      <constructor-arg index="2" ref="contextSource" />
                    </bean>
                </property>
            </bean>
        </constructor-arg>
        <constructor-arg ref="customAuthoritiesPopulator" />
    </bean>
    <s:global-method-security secured-annotations="enabled" />

<!--    <context:annotation-config/>-->

    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
    <bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"/>

    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" />
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"      >
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}" />
    <!--    <property name="password" value="${redis.pass}" />-->
        <property name="poolConfig" ref="poolConfig" />
    </bean>
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"><!-- 最大闲置间隔 -->
        <property name="maxInactiveIntervalInSeconds" value="600"></property>
    </bean>
    <bean class="org.springframework.session.web.http.DefaultCookieSerializer">
        <property name="cookieName" value="JSESSIONID"></property>
        <property name="cookiePath" value="/"></property>
        <!-- <property name="domainName" value="example.com"></property> -->
        <property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$"></property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://1127.0.0.1:3306/xxx"/>
        <property name="username" value="xxx"/>
        <property name="password" value="xxx"/>
    </bean>
</beans>

整合spring-security与spring-session之前,原security的配置保留.因为上一篇security的内容只用登录认证,并没有权限,而在实际项目中,认证使用的ldap,而权限则是从数据库里取的.

2.1数据库获取权限数据 (customAuthoritiesPopulator)

这一步和spring-session关系不大,算是spring-security的延伸.在认证成功之后,LdapAuthenticationProvider 会尝试通过调用配置过的LdapAuthoritiesPopulator加载一系列的用户权限.DefaultLdapAuthoritiesPopulator是它的一个实现类.
你可以通过实现 UserDetailsService 接口来定制认证, JdbcDaoImpl 是它的一个jdbc的实现.在本项目,因为要从数据库获取角色.所以创建了 JdbcDaoImpl 的子类CustomJdbcUserDetailsService.这里重写了加载用户权限的方法loadUserAuthorities(String username).
本例中,由于要从数据库加载权限,所以用CustomAuthoritiesPopulator实现该接口的getGrantedAuthorities(DirContextOperations user, String username) 方法.该方法返回 customJdbcUserDetailsService.loadUserAuthorities(username)的返回值.这样就可以获取数据库里的权限数据了.这里需要注意的是loadUserAuthorities(username)protected的方法,所以自己定义的这两个类要在同一个包下才有权限调用.
数据库的权限表,authority内容默认要带ROLE_,不然应用会不认

REATE TABLE `authorities` (
  `username` varchar(255) DEFAULT NULL,
  `authority` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

因为sql都是在JdbcDaoImpl里,其实也可以自己定义,只要把对应的方法写对就行了
通过设置access的值,给url设置权限<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />在这里可以不用ROLE_前缀.

    @Secured("ROLE_ADMIN")
    public Object test(String v)
    {
        return "v="+v;
    }

通过给方法加@Secured注解,也可以实现方法级别的权限控制.


自定义的两个类

public class CustomJdbcUserDetailsService extends JdbcDaoImpl
{
    @Override
    protected List<GrantedAuthority> loadUserAuthorities(String username)
    {
        return super.loadUserAuthorities(username);
    }
}
public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator
{
    private static Logger logger = LoggerFactory.getLogger(CustomAuthoritiesPopulator.class);

    private CustomJdbcUserDetailsService customJdbcUserDetailsService;

    public CustomAuthoritiesPopulator(CustomJdbcUserDetailsService customJdbcUserDetailsService)
    {
        this.customJdbcUserDetailsService = customJdbcUserDetailsService;
    }

    public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username)
    {

        return customJdbcUserDetailsService.loadUserAuthorities(username);
    }
}

2.2redis连接配置

redis连接把对应参数配置好,在定义一个最大闲置时间间隔,就可以用了

<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" />
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"      >
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}" />
    <!--    <property name="password" value="${redis.pass}" />-->
        <property name="poolConfig" ref="poolConfig" />
    </bean>
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="600"></property>
    </bean>

2.3定制cookie

因为后端是有多个应用的,默认cookie的path是app/,所以要修改成/.这里还可以定制认证cookie的名字.spring-session默认的是session,我们这里依据spring-security,还是继续使用JESSIONID.

<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
        <property name="cookieName" value="JSESSIONID"></property>
        <property name="cookiePath" value="/"></property>
        <!-- <property name="domainName" value="example.com"></property> -->
        <property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$"></property>
    </bean>

3.web.xml

web.xml里面只要增加一个filter,这里需要注意的是security和session使用的是同一个filter-class的类,但是作用不一样,所以都要写,而且springSecurityFilterChain的filter-mapping要在下.最后是org.springframework.web.context.ContextLoaderListener加载文件

  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
  </filter-mapping>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

4.分布式

在其他项目中也配置security和session,未登录跳转到与之前同一个登录页面. web.xml也与第一个项目一样配置即可.applicationContext.xml大同小异.<authentication-manager>不用再配置具体的验证,程序会自己去redies获取session.下面是其他应用的applicationContext.xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:s="http://www.springframework.org/schema/security"
       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
       ">
    <s:http  entry-point-ref="restAuthenticationEntryPoint"    >
        <s:csrf disabled="true"/>
        <s:form-login authentication-success-handler-ref="mySuccessHandler" authentication-failure-handler-ref="myFailureHandler"  default-target-url="/t3.html" />
        <s:logout delete-cookies="JSESSIONID"/>
        <!--    <s:session-management invalid-session-url="/t1.html" >
                &lt;!&ndash; 第二个人登录,第一个人的认证会失效 &ndash;&gt;
                <s:concurrency-control max-sessions="1" />
                &lt;!&ndash; 第一个人登录后,第二个人无法用相同账户登录 &ndash;&gt;
                &lt;!&ndash;<s:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>&ndash;&gt;
            </s:session-management>-->


        <s:intercept-url pattern="/login*"   access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <s:intercept-url pattern="/secure/**"  access="permitAll"/>
        <s:intercept-url pattern="/admin/**" access="hasRole('admin')"  />

        <s:intercept-url pattern="/**" access="authenticated"/>

    </s:http>

    <bean id="mySuccessHandler" class="com.xxx.security.RestAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/index.html"/>
    </bean>
    <bean id="myFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>

    <s:authentication-manager>
    </s:authentication-manager>



    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" />
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"      >
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}" />
        <!--    <property name="password" value="${redis.pass}" />-->
        <property name="poolConfig" ref="poolConfig" />
    </bean>
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="600"></property>
    </bean>

    <bean class="org.springframework.session.web.http.DefaultCookieSerializer">
        <property name="cookieName" value="JSESSIONID"></property>
        <property name="cookiePath" value="/"></property>
        <!-- <property name="domainName" value="example.com"></property> -->
        <property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$"></property>
    </bean>

</beans>

5.遇到的一些问题

ERR_TOO_MANY_REDIRECTS

在配置过程中,难免需要参考一些sample,当时在github上找到一个,它自己写了一个SessionServlet.把请求重定向到contextPath+"/",结果就是程序访问请求都被重定向 ‘app/’,最后就出不来了.它这个具体干嘛的,其实没搞清楚,但是还是值得记录一下.

@WebServlet("/session")
public class SessionServlet extends HttpServlet
{

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        String attributeName = req.getParameter("attributeName");
        String attributeValue = req.getParameter("attributeValue");
        req.getSession().setAttribute(attributeName, attributeValue);
        resp.sendRedirect(req.getContextPath() + "/");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        String attributeName = req.getParameter("attributeName");
        String attributeValue = req.getParameter("attributeValue");
        req.getSession().setAttribute(attributeName, attributeValue);
        resp.sendRedirect(req.getContextPath() + "/");
    }

    private static final long serialVersionUID = 2878267318695777395L;
}
 <servlet>
    <servlet-name>session</servlet-name>
    <servlet-class>com.xx.xx.security.SessionServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>session</servlet-name>
    <url-pattern>/session</url-pattern>
  </servlet-mapping>

spring3.1.1.release

由于有多个应用,其中部分使用的springframework版本是3.1.1.release.这里使用的spring-security也是3.1.1.release.这个版本和4.0.3.release的登录接口不一样.(目前还无法将security-3.1.1和session整合)
3.1.1的登录接口和参数名

<form action="xxxx/j_spring_security_check" method="post">
    用户名:<input type="text" name="j_username" />
    密码:<input type="password" name="j_password" />
    <button type="submit">登录</button>

</form>
Spring Boot和Spring Security是非常常用的Java框架和库,用于构建安全和可扩展的Web应用程序。JWT(JSON Web Token)是一种用于在网络应用间安全传递身份验证和声明信息的标准。 要在Spring Boot中使用Spring Security和JWT,你需要进行以下步骤: 1. 添加依赖:在你的项目的pom.xml文件中添加Spring Security和JWT的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建JWT工具类:编写一个JWT工具类,用于生成和解析JWT。 ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.core.userdetails.UserDetails; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtUtil { private static final String SECRET_KEY = "your-secret-key"; private static final long EXPIRATION_TIME = 864_000_000; // 10 days public static String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String extractUsername(String token) { return extractClaims(token).getSubject(); } public static Date extractExpiration(String token) { return extractClaims(token).getExpiration(); } private static Claims extractClaims(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } } ``` 3. 配置Spring Security:创建一个继承自WebSecurityConfigurerAdapter的配置类,用于配置Spring Security。 ```java 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.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Bean public PasswordEncoder passwordEncoder() { return new Pbkdf2PasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 4. 创建UserDetails实现类:实现Spring Security的UserDetails接口,用于获取用户信息。 ```java import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; public class CustomUserDetails implements UserDetails { private String username; private String password; public CustomUserDetails(String username, String password) { this.username = username; this.password = password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(() -> "ROLE_USER"); } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } // Other UserDetails methods... @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } ``` 5. 创建认证和授权的Controller:创建一个RestController,用于处理用户认证和授权的请求。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody AuthRequest authRequest) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); String token = JwtUtil.generateToken(userDetails); return ResponseEntity.ok(token); } } class AuthRequest { private String username; private String password; // getters and setters... } ``` 这样,你就可以在Spring Boot应用程序中使用Spring Security和JWT来实现认证和授权了。当用户登录时,会生成一个JWT,并在以后的请求中使用该JWT进行身份验证。 请注意,以上代码只是一个简单的示例,你可能需要根据你的实际需求进行适当的修改和扩展。另外,确保保护敏感信息(如密钥)的安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值