查看原文
其他

Spring Security 简单教程以及实现完全前后端分离

点击上方 "程序员小乐"关注公众号, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

Leave all your unhappiness to yesterday, give all your hopes to tomorrow, and do all your hard work today.

把所有的不快给昨天,把所有的希望给明天,把所有的努力给今天。


每日掏心话

人生在世,谁都不易,生活在外,谁都有泪。我们无须在意别人的评说,只要把自己的事情做好;无须看别人的眼神,只需走自己的路。


来自:ming-question | 责编:乐乐

链接:cnblogs.com/ming-question/p/11165775.html

程序员小乐(ID:study_tech)第 660 次推文   图片来自网络


往日回顾:一份从0到1的Java项目实践清单,看这篇就对了!



   正文   

  

Spring Security是spring家族的一个安全框架,入门简单。对比shiro,它自带登录页面,自动完成登录操作。权限过滤时支持http方法过滤。

在新手入门使用时,只需要简单的配置,即可实现登录以及权限的管理,无需自己写功能逻辑代码。

但是对于现在大部分前后端分离的web程序,尤其是前端普遍使用ajax请求时,spring security自带的登录系统就有一些不满足需求了。

因为spring security有自己默认的登录页,自己默认的登录控制器。而登录成功或失败,都会返回一个302跳转。登录成功跳转到主页,失败跳转到登录页。如果未认证直接访问也会跳转到登录页。但是如果前端使用ajax请求,ajax是无法处理302请求的。前后端分离web中,规范是使用json交互。我们希望登录成功或者失败都会返回一个json。况且spring security自带的登录页太丑了,我们还是需要使用自己的。

Spring Security一般简单使用

web的安全控制一般分为两个部分,一个是认证,一个是授权。

认证管理

就是认证是否为合法用户,简单的说是登录。一般为匹对用户名和密码,即认证成功。

在spring security认证中,我们需要注意的是:哪个类表示用户?哪个属性表示用户名?哪个属性表示密码?怎么通过用户名取到对应的用户?密码的验证方式是什么?

只要告诉spring security这几个东西,基本上就可以了。

import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
}

事实上只要继承WebSecurityConfigurerAdapter ,spring security就已经启用了,当你访问资源时,它就会跳转到它自己默认的登录页。但是这还不行,

当用户点击登录时,

1.它会拿到用户输入的用户名密码;

2.根据用户名通过UserDetailsService 的 loadUserByUsername(username)方法获得一个用户对象;

3.获得一个UserDetails 对象,获得内部的成员属性password;

4.通过PasswordEncoder 的 matchs(s1, s2) 方法对比用户的输入的密码和第3步的密码;

5.匹配成功;

 

所以我们要实现这三个接口的三个方法:

1.实现UserDetailsService ,可以选择同时实现用户的正常业务方法和UserDetailsService ;

例如:UserServiceImpl implement IUserService,UserDetailsService {}

2.实现UserDetails ,一般使用用户的实体类实现此接口。

其中有getUsername(), getPassword(), getAuthorities()为获取用户名,密码,权限。可根据个人情况实现。

3.实现PasswordEncoder ,spring security 提供了多个该接口的实现类,可百度和查看源码理解,也可以自己写。

三个实现类的配置如下:

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(NoOpPasswordEncoder.getInstance()); }}

其中Userdetails 为UserDetailsService 中 loadUserByUsername() 方法的返回值类型。

 

到目前为止,就可以完成简单认证了。而授权管理,到现在,是默认的:所有资源都只有‘认证’权限,所有用户也只有‘认证’权限。即,经过认证就可以访问所有资源。

以上,就是spring security的简易应用。可以实现一个稍微完整的安全控制。非常简单。 

授权管理

授权管理,是在已认证的前提下。用户在认证后,根据用户的不同权限,开放不同的资源。

根据RBAC设计,用户有多个角色,角色有多个权限。(真正控制资源的是权限,角色只是一个权限列表,方便使用。

每个用户都有一个权限列表,授权管理,就是权限和资源的映射。在编程中,写好对应关系。然后当用户请求资源时,查询用户是否有资源对应的权限决定是否通过。

权限写在数据库,配置文件或其他任何地方。只要调用loadUserByUsername()时返回的UserDetails对象中的getAuthorities()方法能获取到。

所以无论用户的权限写在哪里,只要getAuthorities()能得到就可以了。

举例:

授权管理映射:add==/api/add,query==/api/query;

数据库中存储了用户权限:query;

那么该用户就只能访问/api/query,而不能访问/api/add。

授权管理配置如下:

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;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.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(NoOpPasswordEncoder.getInstance()); }
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(HttpMethod.POST, "/api/data").hasAuthority("add") .antMatchers(HttpMethod.GET, "/api/data").hasAuthority("query") .antMatchers("/home").hasAuthority("base"); }}

以上就是spring security的基本应用。下面是解决前后端分离下的无法302跳转的情况。

需求是:前后端分离,需要自己的登录页面,使用ajax请求。

出现问题:自己的登录页面请求登录后,后端返回302跳转主页,ajax无法处理;未认证请求资源时,后端返回302跳转登录页,也无法处理。

解决思想:修改302状态码,修改为401,403或者200和json数据。

 

HttpSecurity 有很多方法,可以看一看

比如 设置登录页(formLogin().loginPage("/login.html")) 可以设置自己的登录页(该设置主要是针对使用302跳转,且有自己的登录页,如果不使用302跳转,前后端完全分离,无需设置)。

比如 设置认证成功处理

比如 设置认证失败处理

比如 设置异常处理

比如 设置退出成功处理

可以继承重写其中的主要方法(里面有httpResponse对象,可以随便返回任何东西)

例如:

import org.springframework.http.HttpStatus;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.stereotype.Component;
import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
@Componentpublic class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setStatus(HttpStatus.OK.value()); }}

设置完成登录成功和失败处理后,还是不够满足需求,当用户未通过登录页进入网站,我们需要在用户直接访问资源时,告诉前端此用户未认证。(默认是302跳转到登录页)。我们可以改成返回403状态码。

这里就需要实现一个特殊的方法:AuthenticationEntryPoint 接口的 commence()方法。

这个方法主要是,用户未认证访问资源时,所做的处理。

spring security给我们提供了很多现成的AuthenticationEntryPoint 实现类,

比如默认的302跳转登录页,比如返回403状态码,还比如返回json数据等等。当然也可以自己写。和上面的登录处理一样,实现接口方法,将实现类实例传到配置方法(推荐spring注入)。

如下:

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;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.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {
@Qualifier("userService") @Autowired private UserDetailsService userDetailsService;
@Autowired private LoginSuccessHandler loginSuccessHandler;
@Autowired private LoginFailureHandler loginFailureHandler;
@Autowired private MyLogoutHandler logoutHandler;
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(NoOpPasswordEncoder.getInstance()); }
@Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginProcessingUrl("/login") // 登录成功 .successHandler(loginSuccessHandler) // 登录失败 .failureHandler(loginFailureHandler).permitAll() .and() // 注销成功 .logout().logoutSuccessHandler(logoutHandler) .and() // 未登录请求资源 .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()) .and() .authorizeRequests() .antMatchers(HttpMethod.POST, "/api/data").hasAuthority("add") .antMatchers(HttpMethod.GET, "/api/data").hasAuthority("query") .antMatchers("/home").hasAuthority("base"); }}

以上就算是完了,前端发起ajax请求时,后端会返回200,401,403状态码,前端可根据状态码做相应的处理。

以下是我的全部代码(后端,安全管理demo)

https://github.com/Question7/spring-security-demo

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

SSM 框架!实现分页和搜索分页

学界:为代码自动添加注释,让 Java 程序的阅读和开发更高效

在 JVM 眼中 .class 文件是什么样的?值得收藏!


关注微信公众号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存