网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容 如果右边没有就找找左边
Spring Security是一个高度自定义的安全框架。利用Spring IOC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。 使用Spring Security的原因:javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景。同时认识到他们在WAR或EAR级别无法移植。如果你更换服务器环境,需要花费大量精力重新配置你的应用程序。使用Spring Security解决了这些问题,也提供了很多可定制的安全功能 Spring Security重要核心功能:“认证"和"授权”。 通俗讲,认证就是你能不能进我家,授权就是,你进我家能干什么,比如我就授权你在我家客厅随意活动,那么其它地方你就不能去,也不能操作 不通俗讲,认证:是建立一个声明主体的过程(一个主体代表一个用户、设备或一些可以在你程序中执行动作的其它系统),决定这些主体是否可以登录系统。授权:确定一个主体是否允许在你的应用程序中执行一个动作的过程。就是判断用户是否有权限去做某些事
2003年底,以“The Acegi Security System for Spring”的名字出现,前身为acegi项目 起因是Spring开发者邮件列表中的一个问题:有人提问是否考虑提供一个基于Spring的安全实现。之后因为时间问题,开发出了一个简单的安全实现,没有深入研究 几周后,Spring社区中其他成员同样询问安全问题,就将代码开源了出去。 2004年1月左右,20人左右使用这个项目,随着更多人的加入,2004年3月左右,sourceforge中建立了一个项目acegi,这时并没有认证模块,所有认证功能都是依赖容器完成。而acegi则注重授权。 之后使用的人越来越多,基于容器实现认证显现不足,acegi中也加入了认证功能。一年后,acegi成为Spring子项目 2006年5月,acegi 1.0.0版本发布。2007年底acegi更名为Spring Security
一、先写一个简单Spirng Security项目
和普通spring boot项目不同的地方,就是多引入一个security的包
< parent>
< groupId> org. springframework. boot< / groupId>
< artifactId> spring- boot- starter- parent< / artifactId>
< version> 2.1 .11 . RELEASE< / version>
< / parent>
< dependencies>
< dependency>
< groupId> org. springframework. boot< / groupId>
< artifactId> spring- boot- starter- web< / artifactId>
< / dependency>
< dependency>
< groupId> org. springframework. boot< / groupId>
< artifactId> spring- boot- starter- security< / artifactId>
< / dependency>
< / dependencies>
启动类 controller(简单编写,然后运行项目,复制打印到控制台的密码)
二、自定义认证等逻辑
1. UserDetailsService自定义登录逻辑
由前面的案例可知,我们什么都没有配置时,账号密码都是Spring Security帮我们生成,实际项目都是查数据库,所以我们需要自定义逻辑,控制认证。 需要自定义逻辑非常简单,只需要实现UserDetailsService即可
UserDetailsService
可见这个接口,只定义个一个方法,根据方法名,可以判断是根据用户名,加载用户,返回值是UserDetails
类型
UserDetails
它封装了用户的基本信息 但它是一个接口,想要返回它,必须通过实现类
public interface UserDetails extends Serializable {
Collection < ? extends GrantedAuthority > getAuthorities ( ) ;
String getPassword ( ) ;
String getUsername ( ) ;
boolean isAccountNonExpired ( ) ;
boolean isAccountNonLocked ( ) ;
boolean isCredentialsNonExpired ( ) ;
boolean isEnabled ( ) ;
}
import org. springframework. security. core. GrantedAuthority ;
import org. springframework. security. core. authority. AuthorityUtils ;
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 ;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername ( String username) throws UsernameNotFoundException {
if ( ! username. equals ( "admin" ) ) {
throw new UsernameNotFoundException ( "用户不存在!!!" ) ;
}
String password = "pwd" ;
List < String > list = new ArrayList < > ( ) ;
list. add ( "admin1" ) ;
list. add ( "admin2" ) ;
StringBuilder stringBuilder = new StringBuilder ( ) ;
for ( int i = 0 ; i< list. size ( ) ; i++ ) {
if ( i== ( list. size ( ) - 1 ) ) {
stringBuilder. append ( list. get ( i) ) ;
} else {
stringBuilder. append ( list. get ( i) + "," ) ;
}
}
System . out. println ( stringBuilder. toString ( ) ) ;
List < GrantedAuthority > grantedAuthorities = AuthorityUtils . commaSeparatedStringToAuthorityList ( stringBuilder. toString ( ) ) ;
UserDetails userDetails = new User ( username, password, grantedAuthorities) ;
return userDetails;
}
}
提示,密码必须是编码加密后的,不能用不编码加密的(其实回头看之前,它提供的密码,就能发现,提供的是编码加密后的密码)
2. PasswordEncoder密码解析器
Spring Security要求容器中必须有PasswordEncoder实例。所以当自定义登录逻辑时,要求必须给容器注入PasswordEncoder的bean对象
PasswordEncoder接口
public interface PasswordEncoder {
String encode ( CharSequence rawPassword) ;
boolean matches ( CharSequence rawPassword, String encodedPassword) ;
default boolean upgradeEncoding ( String encodedPassword) {
return false ;
}
}
同样因为它是接口,我们需要实现类来创建它
BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。 BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10。
2. 创建Security配置类,配置解析器Bean实例,然后注入Spring IOC容器,这是自定义登录逻辑的硬性要求
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. security. crypto. bcrypt. BCryptPasswordEncoder ;
import org. springframework. security. crypto. password. PasswordEncoder ;
@Configuration
public class SecurityConfig {
@Bean
protected PasswordEncoder passwordEncoder ( ) {
return new BCryptPasswordEncoder ( ) ;
}
}
@Autowired
private PasswordEncoder passwordEncoder;
String password = "pwd" ;
String encodePassword = passwordEncoder. encode ( password) ;
System . out. println ( password+ "加密后-------------" + encodePassword) ;
boolean matches = passwordEncoder. matches ( password, encodePassword) ;
System . out. println ( "原密码和加密后是否匹配-------" + matches) ;
UserDetails userDetails = new User ( username, encodePassword, grantedAuthorities) ;
3. 连接数据库实现自定义登录逻辑
依赖
< ! -- myBatis -- >
< dependency>
< groupId> org. mybatis. spring. boot< / groupId>
< artifactId> mybatis- spring- boot- starter< / artifactId>
< version> 1.3 .2 < / version>
< / dependency>
< ! -- mysql-- >
< dependency>
< groupId> mysql< / groupId>
< artifactId> mysql- connector- java< / artifactId>
< version> 8.0 .19 < / version>
< / dependency>
< ! -- apace commons工具-- >
< dependency>
< groupId> commons- io< / groupId>
< artifactId> commons- io< / artifactId>
< version> 2.11 .0 < / version>
< / dependency>
mapper接口 xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace = " com.yzpnb.mapper.TUserMapper" >
< select id = " selectByUsername" parameterType = " java.lang.String" resultType = " com.yzpnb.pojo.TUser" >
select id,username,password from t_user where username=#{username}
</ select>
</ mapper>
配置文件
spring :
datasource :
driver-class-name : com.mysql.cj.jdbc.Driver
url : jdbc: mysql: //192.168.47.1: 3306/dubbo_demo? serverTimezone=CST&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
username : root
password : 123456
mybatis :
type-handlers-package : com.yzpnb.pojo
mapperLocations : classpath: mybatis/*Mapper.xml
启动类
import com. yzpnb. mapper. TUserMapper ;
import com. yzpnb. pojo. TUser ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. security. core. GrantedAuthority ;
import org. springframework. security. core. authority. AuthorityUtils ;
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 org. springframework. security. crypto. password. PasswordEncoder ;
import org. springframework. stereotype. Service ;
import java. util. List ;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private TUserMapper tUserMapper;
@Override
public UserDetails loadUserByUsername ( String username) throws UsernameNotFoundException {
TUser tUser = tUserMapper. selectByUsername ( username) ;
if ( tUser == null ) {
System . out. println ( "用户不存在" ) ;
throw new UsernameNotFoundException ( "用户不存在" ) ;
}
System . out. println ( "查询到用户信息------用户名:" + username+ "--------密码:" + tUser. getPassword ( ) ) ;
List < GrantedAuthority > grantedAuthorities = AuthorityUtils . commaSeparatedStringToAuthorityList ( "admin1,admin2" ) ;
UserDetails userDetails = new User ( username, tUser. getPassword ( ) , grantedAuthorities) ;
return userDetails;
}
}
4. 自定义登录页面
引入thymeleaf依赖 在resources下创建templates文件夹,然后创建login.html页面编写代码作为登录页面
<! DOCTYPE html >
< html lang = " en" >
< head>
< meta charset = " UTF-8" >
< title> Title</ title>
</ head>
< body>
< form action = " /login" method = " post" >
username:< input type = " text" name = " username" /> < br/>
password:< input type = " password" name = " password" /> < br/>
< input type = " submit" /> < br/>
</ form>
</ body>
</ html>
再创建一个index.html 作为登录成功后,转到的页面
import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
@Controller
public class SpringSecurityDemoController {
@RequestMapping ( "showLogin" )
public String showLogin ( ) {
return "login" ;
}
@RequestMapping ( "loginSuccess" )
public String loginSuccess ( ) {
return "index" ;
}
}
1. 重点(Spring Security最核心地方)配置类修改
配置类中,设置登录页面。需要继承WebSecurityConfigurerAdapter,并重写configure方法,常用设置如下
successForwardUrl() :登录成功后跳转地址loginPage() :登录页面loginProcessingUrl() :登录页面表单提交地址,此地址可以不真实存在antMatchers() :匹配内容permitAll() :允许authorizeRequests() :授权相关的formLogin() :所有和表单有关系的操作
protected void configure ( HttpSecurity http) throws Exception {
logger. debug ( "Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)." ) ;
http
. authorizeRequests ( )
. anyRequest ( ) . authenticated ( )
. and ( )
. formLogin ( ) . and ( )
. httpBasic ( ) ;
}
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. security. config. annotation. web. builders. HttpSecurity ;
import org. springframework. security. config. annotation. web. configuration. WebSecurityConfigurerAdapter ;
import org. springframework. security. crypto. bcrypt. BCryptPasswordEncoder ;
import org. springframework. security. crypto. password. PasswordEncoder ;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
protected PasswordEncoder passwordEncoder ( ) {
return new BCryptPasswordEncoder ( ) ;
}
@Override
protected void configure ( HttpSecurity http) throws Exception {
http. formLogin ( )
. loginPage ( "/showLogin" )
. loginProcessingUrl ( "/login" )
. successForwardUrl ( "/loginSuccess" ) ;
http. authorizeRequests ( )
. antMatchers ( "/showLogin" ) . permitAll ( )
. anyRequest ( ) . authenticated ( ) ;
http. csrf ( ) . disable ( ) ;
}
}
访问showLogin,会直接放行,然后进入controller,在里面直接重定向到了登录页面 登录成功,地址栏会显示我们设定URL,同时请求/login,那么检测到url中有login,就会执行我们自定义登录逻辑,然后重定向到/loginSuccess,这个请求里面重定向了index.html页面
2. 登录失败页面
我们上面如果登录失败,依然会在登录页面,现在我们想实现,登录失败,进入错误页面
http. formLogin ( )
. loginPage ( "/showLogin" )
. loginProcessingUrl ( "/login" )
. successForwardUrl ( "/loginSuccess" )
. failureForwardUrl ( "/loginFail" )
;
3. 自定义登录参数
前面我们讲,必须用username和password两个参数,那么也可以通过参数设置
http. formLogin ( )
. loginPage ( "/showLogin" )
. loginProcessingUrl ( "/login" )
. successForwardUrl ( "/loginSuccess" )
. failureForwardUrl ( "/loginFail" )
. usernameParameter ( "un" )
. passwordParameter ( "pwd" )
;
4. 解决重复提交表单问题,并且可以站外转发(前后端分离项目使用)
当我们登录成功进入页面,刷新时,会提示重新提交表单,接下来解决这个问题
首先,页面跳转,会重发请求,但是重定向不会,所以我们当前使用的都是跳转,我们需要改成重定向
. successHandler ( new AuthenticationSuccessHandler ( ) {
@Override
public void onAuthenticationSuccess ( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException , ServletException {
httpServletResponse. sendRedirect ( "/loginSuccess" ) ;
}
} )
. failureHandler ( new AuthenticationFailureHandler ( ) {
@Override
public void onAuthenticationFailure ( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException , ServletException {
httpServletResponse. sendRedirect ( "/loginFail" ) ;
}
} )
http. authorizeRequests ( )
. antMatchers ( "/showLogin" , "/loginFail" ) . permitAll ( )
. anyRequest ( ) . authenticated ( ) ;
那么如何站外跳转呢?毕竟现在都是前后端分离项目,页面不是和你项目写在一起的
. successHandler ( new AuthenticationSuccessHandler ( ) {
@Override
public void onAuthenticationSuccess ( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException , ServletException {
User user = ( User ) authentication. getPrincipal ( ) ;
System . out. println ( user. getUsername ( ) ) ;
System . out. println ( user. getPassword ( ) ) ;
System . out. println ( user. getAuthorities ( ) ) ;
httpServletResponse. sendRedirect ( "http://www.baidu.com" ) ;
}
} )
5. 介绍一些和url,静态资源有关的东西,不常用就不详细介绍了
public C antMatchers(String… antPatterns) :前面我们的授权配置方法
可见参数是不定长参数,用于匹配URL规则,如下:
?:匹配一个字符 *:匹配0个或多个字符 **:匹配0个或多个目录
实际项目中,需要放行所有静态资源时,就可以这样写,比如放行js文件夹下所有js脚本
. antMatchers ( "/js/**" ) . permitAll ( )
. antMatchers ( "/**/*.js" ) . permitAll ( )
6. 异常403处理方案
使用Spring Security 时经常会看见403(无权限),默认情况下显示效果如下 而实际项目中可以都是一个异步请求,显示上述效果对用户很不友好(比如整个系统,你只有一个菜单没有权限进去,这时你不小心点了一些,直接跳出一个403页面,感觉是很不好的,一般都是弹出一个对话框,提示一下没有权限),Spring Security支持自定义权限受限
1. 设置一个拒绝一切用户访问的url,以实现403页面的出现
. antMatchers ( "abc" ) . denyAll ( )
import org. springframework. security. access. AccessDeniedException ;
import org. springframework. security. web. access. AccessDeniedHandler ;
import org. springframework. stereotype. Component ;
import javax. servlet. ServletException ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
import java. io. PrintWriter ;
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle ( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException , ServletException {
httpServletResponse. setStatus ( HttpServletResponse . SC_FORBIDDEN) ;
httpServletResponse. setHeader ( "Content-Type" , "application/json;charset=utf-8" ) ;
PrintWriter out = httpServletResponse. getWriter ( ) ;
out. write ( "{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}" ) ;
out. flush ( ) ;
out. close ( ) ;
}
}
先引入组件 配置异常处理
http. exceptionHandling ( )
. accessDeniedHandler ( myAccessDeniedHandler) ;
另外,同步的话,就是不前后端分离,也可以写一个controller来处理,但现在基本没用
http. exceptionHandling ( )
. accessDeniedPage ( "/showException" ) ;
三、自定义授权、访问控制等逻辑
1. 利用角色的权限,实现限制url
Spring Security提供了很多权限控制,比如一个用户登录后,必须具备admin1权限,才可访问/successLogin页面,如果它只有admin2权限,那么就无法访问 用户权限是在自定义登录逻辑中,创建User对象时指定的,权限字符严格区分大小写
1. 配置url访问权限的常用方法(在配置类中配置)
hasAuthority(String) :必须具备指定权限
,才可访问
. antMatchers ( "/loginSuccess" ) . hasAuthority ( "admin1" )
hasAnyAuthority(String…) :具备给定权限
中的某一个,就可以访问
. antMatchers ( "/loginSuccess" ) . hasAnyAuthority ( "admin1" , "admin2" )
hasRole(String) :具备给定角色
就可以访问,否则出现403
给用户赋予角色,也是创建User对象时指定,和权限放在一起即可 赋予角色时,需要以ROLE_开头,后面添加角色名,例如ROLE_manager,其中manager是角色名,ROLE_是固定的前缀。(因为和权限放在一起,程序分不清谁是权限,谁是角色,所以角色加个前缀,方便区分) 但是使用hasRole()这个方法时,参数直接传manager,前缀ROLE_不需要指定,否则报错
. antMatchers ( "/loginSuccess" ) . hasRole ( "manager" )
hasAnyRole(String…) :具备给定角色
中的某一个,就可以访问haslpAddress(String) :请求的是指定ip
,就可以访问
可以通过request.getRemoteAddr()获取ip 注意:本机测试时,localhost和127.0.0.1输出ip地址不一样 当浏览器通过localhost进行访问时,控制台打印内容是getRemoteAddr:0:0:0:0:0:0:0:1 当浏览器通过127.0.0.1访问时控制台打印内容时:getRemoteAddr:127.0.0.1 如果是普通ip192.168.xxx.xxx之类的,都会原样输出:getRemoteAddr:192.168.xxx.xxx
2. 设置访问/loginSuccess这个url必须具有admin1权限,访问/abc必须具有admin3
封装权限 配置
3. 设置访问/loginSuccess这个url必须具有manager角色,访问/abc必须具有abc角色
配置类
4. 设置访问/loginSuccess这个url必须使用127.0.0.1ip,访问/abc必须使用ip192.168.0.1
2. 连接数据库实现权限认证
1. rbac表设计
这个表可能有些难理解,结合想要实现的场景来分析一些
首先一个系统,有很多菜单,比如用户管理,用户管理下有很多子菜单,比如学生管理,讲师管理 那么如何确定父子关系呢,这里就是采用码表的方式,通过父id来确定自己的父亲 顶级菜单父id为0,它的子菜单的父id就是这个顶级菜单的id,依次类推,除了顶级菜单父id为0外,其它菜单项的父id都会记录自己父菜单的id 除了菜单还有很多按钮,比如学生管理菜单下,有新增,删除按钮,那么必须在数据库指定这些菜单的类型,所以我们通过一些特定字符来表示一个菜单的类型,比如menu就是菜单,button就是按钮,或者分的更细,student就是操作学生的按钮 每个菜单都应该具备自己的权限,只有用户拥有相应权限,才能操作菜单,所以数据库中应该有一个权限字段,保存菜单权限 额外的,我们想实现,用户进入系统,只能看到自己有权限操作的菜单,比如一个普通用户,只能查看菜单,不能使用增删改查之类的操作按钮 那么我们就应该建立一个关系表,决定一个角色能够操作哪些菜单
2. mapper代码,实现对rbac表的数据库操作
为了省事,就不给每张表建立实体类了,直接用List
sql语句测试
xml文件编写
<?xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace = " com.yzpnb.mapper.TUserMapper" >
< select id = " selectByUsername" parameterType = " java.lang.String" resultType = " com.yzpnb.pojo.TUser" >
select id,username,password from t_user where username=#{username}
</ select>
< select id = " selectRoleById" parameterType = " java.lang.Integer" resultType = " java.lang.String" >
select
name
from
t_user_role as tur
left join
t_role as r
on
tur.rid = r.id
where
tur.uid = #{userId}
</ select>
< select id = " selectPermissionsByUserId" parameterType = " java.lang.Integer" resultType = " java.lang.String" >
select
m.permission as permission
from
t_user_role as tur
left join
t_role as r
on
tur.rid = r.id
left join
t_role_menu as trm
on
r.id = trm.rid
left join
t_menu as m
on
m.id = trm.mid
where
tur.uid = #{userId}
</ select>
</ mapper>
3. 重写登录授权逻辑代码和配置类,测试
List < String > roles = tUserMapper. selectRoleById ( tUser. getId ( ) ) ;
List < String > permissions = tUserMapper. selectPermissionsByUserId ( tUser. getId ( ) ) ;
StringBuilder stringBuilder = new StringBuilder ( ) ;
for ( int i = 0 ; i< roles. size ( ) ; i++ ) {
if ( i == roles. size ( ) - 1 ) {
if ( permissions. size ( ) > 0 ) stringBuilder. append ( "ROLE_" + roles. get ( i) + "," ) ;
else stringBuilder. append ( "ROLE_" + roles. get ( i) ) ;
} else
stringBuilder. append ( "ROLE_" + roles. get ( i) + "," ) ;
}
for ( int i = 0 ; i< permissions. size ( ) ; i++ ) {
if ( i == permissions. size ( ) - 1 ) stringBuilder. append ( permissions. get ( i) ) ;
else stringBuilder. append ( permissions. get ( i) + "," ) ;
}
List < GrantedAuthority > grantedAuthorities =
AuthorityUtils . commaSeparatedStringToAuthorityList ( stringBuilder. toString ( ) ) ;
System . out. println ( "用户的权限字符为:" + stringBuilder) ;
. antMatchers ( "/loginSuccess" ) . hasRole ( "管理员" )
. antMatchers ( "/abc" ) . hasAuthority ( "demo:insert" )
3. 基于表达式的访问控制
之前学习的登录用户权限判断实际上底层都是调用access(表达式),例如hasRole,hasAnyRole等,底层都是调用access()方法, 因为实际项目中,很可能出现Spring Security提供的方法实现不了的需求,这时就要我们通过这个方法,自定义访问控制方法
接口 实现类,实现判断权限列表中是否包含角色管理员,如果有返回true,没有返回false,记住通过@Service注解,将其注入到容器中
import com. yzpnb. service. MyAccessService ;
import org. springframework. security. core. Authentication ;
import org. springframework. security. core. GrantedAuthority ;
import org. springframework. security. core. authority. SimpleGrantedAuthority ;
import org. springframework. security. core. userdetails. UserDetails ;
import org. springframework. stereotype. Service ;
import javax. servlet. http. HttpServletRequest ;
import java. util. Collection ;
@Service
public class MyAccessServiceImpl implements MyAccessService {
@Override
public boolean hasPermission ( HttpServletRequest request, Authentication authentication) {
Object o = authentication. getPrincipal ( ) ;
if ( o instanceof UserDetails ) {
UserDetails userDetails = ( UserDetails ) o;
Collection < ? extends GrantedAuthority > collection = userDetails. getAuthorities ( ) ;
return collection. contains ( new SimpleGrantedAuthority ( "ROLE_管理员" ) ) ;
}
return false ;
}
}
我们前面讲过,access()方法可以通过表达式控制 那么在@Service注解没有参数的情况下,我们刚才的接口匹配的表达式就是
“@myAccessServiceImpl.hasPermission(request,authentication)”
接下来看怎么具体使用
. antMatchers ( "/loginSuccess" ) . access ( "@myAccessServiceImpl.hasPermission(request,authentication)" )
4. 基于注解的访问控制
虽然提供了一些访问控制注解,但默认是不可用的,需要通过在启动类添加@EnableGlobalMethodSecurity()注解开启后,才能使用,注意,每个注解,都需要单独开启,而且不能共存
这些注解可以写到Service接口或方法上,也可以写到Controller或Controller中的方法上,一般写在Controller的方法上,当客户端请求Controller某一方法,就会进行访问控制 效果就是,条件运行,程序正常执行,条件不允许,会报错500
@Secured :判断是否具有直接角色,参数要以ROLE_开头,可以写在方法或类上@PreAuthorize :访问方法或类之前先判断权限,使用较多,参数就和配置类中使用方法一样@PostAuthorize :方法或类执行结束后判断权限,此注解很少使用,不做讲解
先放行请求 启动类开启注解访问控制功能
@EnableGlobalMethodSecurity ( securedEnabled = true )
测试@Secured(“ROLE_管理员”)
开启注解 测试
5. Remember Me 记住我功能
用户只需要在登录时添加remember-me复选框,取值为true,那么框架会自动把用户信息存储到数据源中,以后就可以不重复登录访问 需要添加额外依赖,底层依赖Spring-JDBC,但是实际开发中多使用MyBatis框架,而很少直接导入spring-jdbc,所以我们只需要mybatis启动器(这些依赖我们前面都已经加过了)
1. 编写配置类,并注入Bean对象,这里涉及到了自动建表,第一次需要建立,第二次就不需要了,所以我们自动建表一般都手动建立,如果使用自动建立,那么第二次运行需要注释掉自动建表代码
配置类
package com. yzpnb. config ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. security. web. authentication. rememberme. JdbcTokenRepositoryImpl ;
import org. springframework. security. web. authentication. rememberme. PersistentTokenRepository ;
import javax. sql. DataSource ;
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
@Bean
protected PersistentTokenRepository getPersistentTokenRepository ( ) {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl ( ) ;
jdbcTokenRepository. setDataSource ( dataSource) ;
jdbcTokenRepository. setCreateTableOnStartup ( true ) ;
return jdbcTokenRepository;
}
}
spring security 配置类,将登录逻辑和刚刚配置的bean实例注入,配置好,再配置令牌失效时间为120秒
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Autowired
private UserDetailsServiceImpl userDetailsService;
http. rememberMe ( )
. tokenValiditySeconds ( 120 )
. userDetailsService ( userDetailsService)
. tokenRepository ( persistentTokenRepository) ;
3. 重写登录页面,添加Remember me 复选框
remember-me:< input type = " checkbox" name = " remember-me" value = " true" />
运行后,注释自动建表代码 查看效果(数据库令牌添加成功后,无论重启项目多少次,都无需重复登录,只有超过有效时间后才需要重新登录)
四、退出登录
用户只需要发送/logout请求即可,spring security默认退出登录请求为/logout,退出成功后跳转到/login?logout 实现非常简单,在页面中添加/logout超链接即可
< a href = " /logout" /> 退出登录
但是通常我们为了实现更好的退出效果,会添加退出配置,
http. logout ( )
. logoutUrl ( "/logout" )
. logoutSuccessHandler ( new LogoutSuccessHandler ( ) {
@Override
public void onLogoutSuccess ( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException , ServletException {
httpServletResponse. sendRedirect ( "/showLogin" ) ;
}
} ) ;
五、Spring Security中的CSRF
还记得自定义登录配置时的csrf代码吗?没有内一行代码会导致用户无法被认证 这行代码的含义是,关闭csrf防护
http. csrf ( ) . disable ( ) ;
跨站请求伪造,也称One Click Attack或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。 跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求 客户端与服务器进行交互时,由于http协议本身是无状态协议,所以引入cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份。 跨域情况下,session id可能会被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求合法,可能会被黑客利用
Spring Security4开始CSRF防护默认开启,默认拦截请求,进行CSRF处理。CSRF为了保证不是其它第三方网站访问,要求访问时携带参数名为_csrf,值为token(token在服务端产生)的内容,如果token和服务端保存的token不一致,那么拒绝访问。 所以如果你想使用CSRF的保护,那么你每次发送请求,只要发送一个参数名为_csrf的参数即可,至于参数值,是一个token字符串,需要服务器去生成,返回给你
六、Oauth2 、SpringSecurityOauth2
七、前后端分离,实现登录,权限管理系统