文章目录
SpringSecurity入门
Shiro是轻量级的安全框架,而随着springboot的流行,现在是整合springsecurity,因为是spring家族的组件,整合非常的方便。
SpringSecurity本质上是一个过滤器链,有很多过滤器
实现的功能就是 认证+授权
基本原理
1.过滤器是如何加载的?
使用springSecurity配置过滤器 DelegatingFilterProxy过滤器链Filter对象,然后getFilter完成对所有用到的过滤器的加载。
2.两个重要的接口:
-
UserDetailsService:查询数据库密码的过程。在登录判断的时候会经过一个过滤器usernamePasswordAuthenticationFilter,这个过滤器会通过传过来的request获取用户名和密码字符串,参数名称一定要是username和password,可以自定义设置但是不建议。
接口实现的内容就是查询数据库和密码的过程,里面有一个重要方法是loadUserByUsername(String username) 前端传入输入的用户名,然后去数据库查找用户名数据行的其他数据,例如权限,数据库正确的密码,用户注册时间等属性,封装成一个UserDetails类对象返回。
其实UserDetails也是一个接口,实现类一般是使用User。 -
PasswordEncode:数据加密的接口。springsecurity的实现类使用的是BCryptPasswordEncoder,这个接口实现类有encode加密的方法,有matches解析比较密码的方法。每次加密的结果都不同,因为salt的值是随机的。
认证控制实现案例
创建项目引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.注入密码加密解析器
密码解析器是需要给容器进行管理的,所以我们需要建立一个bean对象:
/*
* security配置类
*/
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
2.登录会先到UserDetailsService接口的方法loadUserByUsername:
建立编写UserDetailServiceImpl类:
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.根据用户名去数据库查找,如果不存在则抛出异常
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
//这里演示加密一次
String password = passwordEncoder.encode("123");
数据库的密码和前端输入的密码,返回User对象
//这里直接返回,则登陆页面我们需要输入 admin 123 才能登录成功
return new User(username,password, AuthorityUtils.
commaSeparatedStringToAuthorityList("admin,normal"));
}
}
3.自定义登录页面:
正常来说我们不想使用springsecurity自带的登录页面,使用自定义的登录页面
可以先创建自定义的登录页面
然后需要在sercurityConfig中加入配置:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//1表单提交
http.formLogin()
//自定义登录页面
.loginPage("/login.html")
//必须和页面的表单提交的接口参数一样,会去执行自定义登录逻辑
.loginProcessingUrl("/login")
//登录成功之后跳转的页面,POST请求,所以不能直接返回页面,而是通过controller请求
.successForwardUrl("/toMain")
//登录失败跳转的页面
.failureForwardUrl("/toError");
//2授权
http.authorizeRequests()
//放行/error.html,不需要认证
.antMatchers("/error.html").permitAll()
//放行/login.html,不需要认证
.antMatchers("/login.html").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
//3关闭csrf防护
http.csrf().disable();
}
}
这样就实现了简单的入门案例,只有账户和密码符合才能放行,并且有特殊的页面不需要认证。
认证功能加入数据库
1引入mybatis场景器
2配置application.yml中的数据库四大参数,创建bean对象
3设置注解开发UsersMapper
4修改UserDetailsServiceImpl 的实现方法
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询数据库内的用户对象
Users users = usersMapper.findUsersById(username);
//查询不到则抛出异常
if(users == null) throw new UsernameNotFoundException("用户不存在");
//从数据库查询到的用户名密码返回
return new User(username,passwordEncoder.encode(users.getPassword()), AuthorityUtils.
commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
}
}
授权控制用户
- peritAll() 全部都放行
- hasAuthority() 指定权限限制方法
- hasAnyAuthority() 任意权限限制方法
- hasRole() 指定角色限制方法
- hasAnyRole() 任意角色限制方法
- hasIpAddress() 指定ip地址限制方法
一.Matchers使用规范:
这个代码执行是有顺序的,如果anyRequest放在第一行,则会直接报错,像这种比较笼统的方法,应该放到后面,让前面的先放行。
- .anyMatchers()里面匹配的目录url位置都会被放行
- .regexMatchers()是匹配正则表达式,正确则放行
- .mvcMatchers() 是加上前缀,用的比较少
如果要加上请求的方法限制,则在上面的两个方法第一个参数里面传入请求的方法类型,如下:
.antMatchers(HttpMethod.POST,"/error.html").permitAll()
补充:对后端所有controller方法都加入前缀/xxxx,
则可以在application.yml中加入 spring.mvc.servlet.path = /xxxx
如果加上这个前缀的话,上面设置放行的路径都要加上/xxxx
.antMatchers(HttpMethod.POST,"/xxxx/error.html").permitAll()
Matchers的方法常用permitAll(),还有denyAll()全部禁止等
二.权限限制实现过程:
1.在上面我们编写的授权就是在securityConfig里面的authorizeRequest方法加入权限代码
.antMatchers("/main1..html").hasAnyAuthority("adm")
2.在UserDetailsService,把返回user对象设置权限,如果权限匹配,则不会报错
三.角色限制实现过程:
1.在securityConfig中加入角色限制代码
//加上角色判断
.antMatchers("/main1..html").hasAnyRole("abc")
2.在UserDetailsService,把返回user对象设置角色ROLE_xxxx
角色与权限的区别在于 ROLE_,都大小写敏感
自定义403页面
1.编写403异常处理器
根据不同的异常状态选中不同的枚举值
完整代码如下:
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//响应403状态
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//编写返回的数据内容
httpServletResponse.setHeader("Content-Type","application/json:charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write("{\"status\":\"error\",\"msg\":\"权限不足\",\"请联系管理员\"}");
writer.flush();
writer.close();
}
}
2.在securityConfig配置类中注入此处理器,并设置
//使用自己的403异常处理器
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
上面的方法是返回一段json字段,如果想要返回一个页面,则需要:
- 1创建一个unauth页面
- 2在securitysconfig中配置:
http.exceptionHandling().accessDeniedPage(“/unauth.html”);
注解简化开发(不建议使用)
上面是使用配置类实现访问控制,也可以使用开启注解,注解方式来实现
@Secured
- 1在启动类上开启注解功能
@EnableGlobalMethodSecurity(secureEnabled=true) - 2在controller方法上面加上注解:
@Secured({“ROLE_sale”,”ROLE_manager”}) sale或manager角色才能访问 - 3在userdetailsService设置用户角色
@PreAuthorize 方法执行之前校验
- 1在启动类上开启注解功能
@EnableGlobalMethodSecurity(secureEnabled=true,prePostEnabled=true) - 2在controller方法上面加上注解:
@PreAuthorize(“hasAnyAuthority(‘admin’)”) admin权限才能访问 - 3在userdetailsService设置用户角色
@PostAuthorize 方法执行之后才进行校验,用法和上面类似
@PostFilter 方法返回数据进行过滤
@PreFilter 传入方法数据进行过滤
注销或退出登录
默认使用/logout自带的接口
在securityconfig配置类内设置跳转地址
//5.退出登录配置
http.logout()
//退出成功后跳转的页面
.logoutSuccessUrl("/login.html");
自动登录RememberMe
Cookie也可以,但是cookie是在客户端实现的,不安全
Springsecurity里面的自动登录原理分析:
功能实现:
1.引入依赖
//mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
//数据库驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
并设置数据源四大参数值
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/yuan?useUnicode=true&characterEncoding=UTF-8&failOverReadOnly=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=yuan
3.在securityConfig内注入bean对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//设置数据源
jdbcTokenRepository.setDataSource(dataSource);
//自动建表,只有第一次需要
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
4.在securityConfig内引入persistentTokenRepository, userdetailService,数据源
@Autowired
private DataSource dataSource; //引入数据源
@Autowired
private UserDetailsService userDetailsService; //引入自定义登录逻辑service
@Autowired
private PersistentTokenRepository persistentTokenRepository;
5.rememberme方法
//4.记住我
http.rememberMe()
//设置数据源
.tokenRepository(persistentTokenRepository)
//超时时间
.tokenValiditySeconds(60)
//自定义登录逻辑
.userDetailsService(userDetailsService);
6.在前端的表格内,加入选项
记住我:<input type="checkbox" name="remember-me" value="true"/><br>
操作成功:
数据库内有表和数据行
客户端也存取了cookie
CSRF跨站请求
CSRF攻击是:
客户端与服务端进行交互时,由于http协议本身是无状态协议,所以引入cookie来作为身份识别,最主要就是存入sessionID,然而在跨域的情况,sessionID可能被黑客截取,那黑客以这个sessionID来请求就可以冒充客户端,达到使用客户端的账户等谋利的行为。
SpringSecurity的CSRF处理就可以防护这种黑客攻击,要求访问时携带参数名为_csrf值为token(token是服务端加密的,截获了也用不了)的内容,如果token和服务端的token匹配成功,则正常访问。
代码实现:
1.引入th标签
xmlns:th="http://www.thymeleaf.org"
2.设置隐藏行
<input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}">
3.把之前设置的关闭csrf代码行注释掉
SpringSecurity微服务实现
Oauth协议
客户端,资源拥有者,验证服务器,资源服务器
授权码模式:客户端通过资源拥有者获取授权许可,然后再通过验证服务器拿到访问令牌,拿着访问令牌就可以通过资源服务器去获取需要的资源
Token令牌类型:
授权码:用于访问验证服务器获取访问令牌或者刷新令牌
访问令牌:用于打开资源服务器获取资源
刷新令牌:访问令牌是有时效的,当访问令牌失效,就会使用刷新令牌重新访问验证服务器获取新的访问令牌和刷新令牌
BearerToken:有这个令牌的任何客户端都能访问资源,相当于现金
认证过程
单点登录:是一种控制多个相关但彼此独立的系统的访问权限,拥有这一权限的用户可以使用单一的ID和密码访问某个或多个系统从而避免使用不同的用户名或密码,或者通过某种配置无缝地登录每个系统。
基于Oauth协议的单点登录sso实现流程:
1第一次登录授权成功后端给前端发送一个token放在在cookie中,并且把用户信息存储在redis中。
2这样之后前端在请求头header中把这个cookie加入发送给后端,后端获取到token,从token获取用户信息去redis比对获取权限列表
3根据拿到的权限列表限制用户执行的操作
如图所示:
整合Springsecurity实现:
1设置建立数据库
2 建立项目结构,引入pom依赖
3 启动redis : /usr/local/bin/redis-server /etc/redis.conf
4 启动nacos: 在nacos 的bin目录下cmd 输入 startup.cmd -m standalone
5 复制service-base的工具类到项目内
6 编写springsecurity授权微服务 *
自定义密码处理工具类,使用MD5工具类加密,注入容器
Token操作工具类,使用JWT工具类进行加密解码,注入容器
退出处理器logout
未授权统一处理类
编写过滤器: 自定义认证过滤器 自定义授权过滤器
核心配置类
7编写UserDetailsService
单点登录SSO总结:
单点登录功能主要是使用session来保存用户信息的,但是多系统之间的session是不共享的,每个服务器存储自己的session,这就意味着在系统A里面生成session,当访问系统B的时候则没有这个session。
因此,我们可以把登录功能抽离出来作为一个系统,登录成功后做两件事,把生成的用户信息保存到redis,把生成的token令牌写到网站的cookie中。
Cookie在跨域的情况也不会共享,因此我们前端发送请求需要强制把客户端cookie里面的token放在请求头来发送。