Spring Security和Shiro
Spring Security 是Spring家族中的一个安全管理框架。
相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
一般来说中大型的项目都是使用SpringSecurity 来做安全框架。
小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
一般来说,常见的安全管理技术栈的组合如下:
SSM + Shiro
Spring Boot/Spring Cloud + Spring Security
但是单纯从技术上来说,无论怎么组合,都是可以运行的,这只是常见的推荐组合。
认证和授权
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
在Spring Security中就具体化为用户认证(Authentication)和用户授权(Authorization)两个部分。
用户认证
验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗的说就是系统认为用户是否能登录。
用户授权
验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对某一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情
SpringBoot整合SpringSecurity
启动项目,控制台中会打印出来密码:
我们在浏览器中输入http://localhost:8080/user/list
直接跳转到http://localhost:8080/login
输入完毕后点击Sign in 说明登录成功 跳转到http://localhost:8080/user/list
运行出结果
修改用户名和密码
spring.security.user.name=root
spring.security.user.password=123456
这个人登录后,他有没有权限去访问相应的请求
授权
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.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/*开启安全管理配置*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override//自定义认证
protected void configure(HttpSecurity http) throws Exception {
//authorizeRequests项目中的所有controller请求
//antMatchers不需要认证
//.permitAll();任何人都可以所以访问
http.authorizeRequests().antMatchers("/login").permitAll();
//验证角色商家可以发布商品
//http.authorizeRequests().antMatchers("/admin/pushGoods").hasRole("shangjia");
//验证有发布商品权限的所以用户
http.authorizeRequests().antMatchers("/admin/add","/delete","/pushGoods").hasAuthority("shangjiaquanxian");
//其他请求需要登录后访问
http.authorizeRequests().anyRequest().authenticated();
//目前使用表单登录
http.formLogin();
}
/*自定义用户权限*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//authorizeRequests项目中的所有controller请求
//antMatchers不需要认证
//.permitAll();任何人都可以所以访问
http.authorizeRequests().antMatchers("/login").permitAll();
//验证角色商家可以发布商品
//http.authorizeRequests().antMatchers("/admin/pushGoods").hasRole("shangjia");
//验证有发布商品权限的所以用户
http.authorizeRequests().antMatchers("/admin/pushGoods").hasAuthority("pushGoods");
//目前使用表单登录
http.formLogin();
}
}
springsecurity的认证授权流程
Spring Security最核心的东西是一个过滤器链,这些过滤器在Spring boot启动的时候会帮我们配置上。
执行流程
文字描述话术:
具体的执行流程其实是一个过滤链:
username=root&8 password=123456
1.UsernamePasswordAuthenticationFilter
拦截 /login post
从这个请求中 根据username 取出了用户名 root
根据password 取出了密码 123456
Authentication a1 = new Authentication0:a1.setUsername("root"):
a1.setPassword("123456)
2.在第一步的方法中
我们继续调用ProviderManager.authenticate(a1 );
3.因为我们是表单提交 所以第三步
ProviderManager.authenticate(a1);
在authenticate内部继续调用DaoAuthenticationProvider.authencate(a1)
4.我们会继续调用InMemoryUserDetailsManager的loadUserByUsername(a1.username)
这个loadUserByUsername方法的返回值中包含 这个用户的用户名 密码 权限 角色信息
这个信息存放在一个叫UserDetails对象 这个对象中有当前登录这个用户的用户名root 密码123456对应的密码 权限角色信息
5.把a1中登录的密码 加密一下和 UserDetails中的密码进行比较 如果一样登录成功否则就是登录失败
6.如果登录成功 我们需要把UserDetails中的权限信息复制一份到a1对象中 返回a1对象
7.把a1对象存放在一个SecurityContext的上下文中?把a1对象存放到session中
1、用户向应用程序发起请求,请求需要经过Spring Security的过滤器链。
2、过滤器链首先会经过UsernamePasswordAuthenticationFilter过滤器,该过滤器判断请求是否是一个认证请求(如何知道是一个认证请求 拦截 对/login 的 POST 请求做拦截,校验表单中用户名,密码)。如果是认证请求,过滤器将获取请求中的用户名和密码,然后使用AuthenticationManager进行身份认证。
3、AuthenticationManager会根据用户名和密码创建一个Authentication对象,并将该对象传递给AuthenticationProvider进行认证。
4、AuthenticationProvider会根据传递过来的Authentication对象进行身份认证,并返回一个认证成功或失败的结果。
5、如果认证成功,UsernamePasswordAuthenticationFilter会将认证信息封装成一个Authentication对象,并将其放入SecurityContextHolder上下文中。
SecurityContextHolder
SecurityContextHolder上下文
session 一个用户在服务器上有一片空间 我们可以向这个空间中存放数据 只要是这个用户发送的请求 都可以共享这个空间的数据
SecurityContextHolder
获取securityContext,SecurityContextHolder的 getContext() 方法
SecurityContext securityContext = SecurityContextHolder.getContext();
1
从securityContext获取Authentication
Authentication authentication = securityContext.getAuthentication()
1
获取用户的信息,也就是UserDetails
UserDetails principal = (UserDetails)authentication.getPrincipal();
SecurityContextHolder 用来获取登录之后用户信息。Spring Security 会将登录用户数据保存在 Session 中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到 SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将 Security SecurityContextHolder 中的数据清空。这一策略非常方便用户在 Controller、Service 层以及任何代码中获取当前登录用户数据。
6、用户请求获取资源时,会经过FilterSecurityInterceptor过滤器,该过滤器会根据请求的URL和HTTP方法获取访问控制列表(Access Control List)。
7、Access Control List会包含访问资源所需要的权限信息,FilterSecurityInterceptor会将Authentication对象和Access Control List传递给AccessDecisionManager进行授权决策。
8、AccessDecisionManager会调用多个AccessDecisionVoter进行投票,并根据投票结果来决定当前用户是否有访问该资源的权限。如果用户被授权访问资源,应用程序将返回资源的响应结果。
总结就是首先经过认证过滤器实现认证,认证成功的话就会将用户信息存到authentication对象里面放到security上下文去(后续的权限校验需要获取到),这里面是包括权限的,之后再由AccessDecisionManager去根据相关策略进行权限鉴定
UsernamePasswordAuthenticationFilter 在org.springframework.security.web.authentication包下 可以查看源码
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
这也是第一个过滤器
过滤器来了先走这个方法
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);// 将登录成功信息存放在
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
}
ProviderManager
ProviderManager 在 org.springframework.security.authentication包下 可以查看源码 他实现了AuthenticationManager这个接口
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
Iterator var9 = this.getProviders().iterator();
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
if (provider.supports(toTest)) {
if (logger.isTraceEnabled()) {
Log var10000 = logger;
String var10002 = provider.getClass().getSimpleName();
++currentPosition;
var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
if (result == null && this.parent != null) {
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
} catch (ProviderNotFoundException var12) {
} catch (AuthenticationException var13) {
parentException = var13;
lastException = var13;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
}
因为是表单提交 所以调用的是DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
String username = this.determineUsername(authentication);
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw var6;
}
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
}
核心组件介绍:
Authentication
Authentication是一个接口,用来表示用户认证信息。
该对象主要包含了用户的详细信息(UserDetails)和用户鉴权时所需要的信息,如用户提交的用户名密码、Remember-me Token,或者digest hash值等。按不同鉴权方式使用不同的Authentication实现。
在用户登录认证之前相关信息会封装为一个Authentication具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的Authentication对象,然后把它保存在 SecurityContextHolder所持有的SecurityContext中,供后续的程序进行调用,如访问权限的鉴定等。
接口中的方法:
从这个接口中,我们可以得到用户身份信息,密码,细节信息,认证信息,以及权限列表,具体的详细解读如下:
getAuthorities(): 用户权限信息(权限列表),通常是代表权限的字符串列表;
getCredentials(): 用户认证信息(密码信息),由用户输入的密码凭证,认证之后会移出,来保证安全性;
getDetails(): 细节信息,Web应用中一般是访问者的ip地址和sessionId;
getPrincipal(): 用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等);
isAuthenticated: 获取当前 Authentication 是否已认证;
setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。
官方文档里说过,当用户提交登陆信息时,会将用户名和密码进行组合成一个实例UsernamePasswordAuthenticationToken,而这个类是Authentication的一个常用的实现类,用来进行用户名和密码的认证,类似的还有RememberMeAuthenticationToken,它用于记住我功能。
GrantedAuthority
Authentication的getAuthorities()方法返回一个 GrantedAuthority 对象数组。
GrantedAuthority该接口表示了当前用户所拥有的权限(或者角色)信息,用于配置 web授权、方法授权、域对象授权等。该属性通常由UserDetailsService 加载给 UserDetails。这些信息由授权负责对象AccessDecisionManager来使用,并决定最终用户是否可以访问某资源(URL或方法调用或域对象)。鉴权时并不会使用到该对象。
如果一个用户有几千个这种权限,内存的消耗将会是非常巨大的。
5.UserDetails
UserDetails存储的就是用户信息,它和Authentication接口类似,都包含了用户名,密码以及权限信息。
而区别就是Authentication中的getCredentials来源于用户提交的密码凭证,而UserDetails中的getPassword取到的则是用户正确的密码信息,认证的第一步就是比较两者是否相同,除此之外,Authentication#getAuthorities是认证用户名和密码成功之后,由UserDetails#getAuthorities传递而来。而Authentication中的getDetails信息是经过了AuthenticationProvider认证之后填充的。
其接口方法含义如下:
getAuthorites:获取用户权限,本质上是用户的角色信息。
getPassword: 获取密码。
getUserName: 获取用户名。
isAccountNonExpired: 账户是否过期。
isAccountNonLocked: 账户是否被锁定。
isCredentialsNonExpired: 密码是否过期。
isEnabled: 账户是否可用。
四、完成自定义的认证授权流程
1.UsernamePasswordAuthenticationFilter这个过滤器如何知道我们提交的是登录操作,
2.去数据库验证用户名和密码的操作应该写在什么地方?
我们需要进行自定义配置
package com.tyhxzy.springsecurity.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
// 开启安全框架
@EnableWebSecurity
// 针对方法开启方法前和方法后的权限验证还有 角色认证
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
/**
* 配置密码解析
* @return
*/
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置用户名和密码
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf防护 跨站请求防护
http.csrf().disable()
//表单登录
.formLogin()
//登录访问路径,与页面表单提交路径一致
.loginProcessingUrl("/login")
.and()
//认证配置
.authorizeRequests()
.antMatchers("/login").permitAll()
//任何请求
.anyRequest()
//都需要身份验证
.authenticated();
//配置退出
http.logout()
//退出路径
.logoutUrl("/logout")
;
}
}
导入以下依赖:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
五张表
登录验证的是t_user表
权限和角色分布在t_role和t_permission表中
实体类的创建(略 )自行完成
对应的mapper文件和映射文件的创建(略)
表数据请参考资料中的offcnpe.sql文件
业务逻辑层的编写如下
我们自定义一个业务逻辑层实现类
package com.tyhxzy.springsecurity.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tyhxzy.springsecurity.entity.*;
import com.tyhxzy.springsecurity.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private TUserMapper userMapper;
@Autowired
private TUserRoleMapper tUserRoleMapper;
@Autowired
private TRoleMapper roleMapper;
@Autowired
private TRolePermissionMapper tRolePermissionMapper;
@Autowired
private TPermissionMapper tPermissionMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
LambdaQueryWrapper<TUser> userquery = new LambdaQueryWrapper<>();
userquery.eq(TUser::getUsername,s);
TUser user = userMapper.selectOne(userquery);
// 存放权限的集合
List<GrantedAuthority> ssp = null;
if(user==null){
throw new UsernameNotFoundException("用户名不存在");
}else{
ssp = new ArrayList<>();
LambdaQueryWrapper<TUserRole> q2 = new LambdaQueryWrapper<>();
q2.eq(TUserRole::getUserId,user.getId());
List<TUserRole> rids = tUserRoleMapper.selectList(q2);
// 遍历中间表 把所有的角色id查询回来
List<Integer> collect = rids.stream().map(sp -> sp.getRoleId()).collect(Collectors.toList());
List<TRole> tRoles = roleMapper.selectBatchIds(collect);
Stream<SimpleGrantedAuthority> simpleGrantedAuthorityStream = tRoles.stream().map(sp1 -> {
return new SimpleGrantedAuthority(sp1.getKeyword());
});
List<SimpleGrantedAuthority> collect1 = simpleGrantedAuthorityStream.collect(Collectors.toCollection(ArrayList::new));
ssp.addAll(collect1);
// 查询权限
// 也是先查询中间表
for(TRole item:tRoles) {
LambdaQueryWrapper<TRolePermission> q3 = new LambdaQueryWrapper<>();
q3.eq(TRolePermission::getRoleId,item.getId());
List<TRolePermission> tRolePermissions = tRolePermissionMapper.selectList(q3);
List<Integer> pids = tRolePermissions.stream().map(sp1 -> sp1.getPermissionId()).collect(Collectors.toList());
List<TPermission> tps = tPermissionMapper.selectBatchIds(pids);
Stream<SimpleGrantedAuthority> rty = tps.stream().map(sp1 -> {
return new SimpleGrantedAuthority(sp1.getKeyword());
});
List<SimpleGrantedAuthority> collect2 = rty.collect(Collectors.toCollection(ArrayList::new));
ssp.addAll(collect2);
}
}
return new User(s,user.getPassword(),ssp);
}
}
controller上编写
package com.tyhxzy.springsecurity.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/list")
@Secured(value = "ROLE_ADMIN")// 访问这个方法之前首先验证这个用户是否是ROLE_ADMIN这个角色
public String hh(){
return "哈哈";
}
@RequestMapping("/list1")
@PreAuthorize("hasAuthority('CHECKITEM_QUERY')") // 访问这个方法前首先验证这个用户是否有CHECKITEM_QUERY的权限
public String hh1(){
return "哈哈";
}
}
在浏览器中访问http://localhost:8080/user/list 因为未登录会直接被spring security打到登录页面,
在登录页面输入 admin 密码是123456 这个人的角色时ROLE_ADMIN可以直接在页面输出哈哈
如果在登录页面输入 xiaoming 密码是123456 这个人的角色不是ROLE_ADMIN 所以会跳转到403页面
自定义登录失败和权限认证失败的内容返回给客户端,不要直接打印出来403页面或者登录失败
AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
package com.tyhxzy.springsecurity.config;
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;
/**
* 认证失败处理类 返回未授权
* 用来解决认证过的用户访问无权限资源时的异常
*/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
response.getWriter().print("没有访问权限!");
}
}
在WebSecurityConfig中添加如下代码:
@Autowired
private CustomAccessDeniedHandler deniedHandler;
// 设置已经登录过 但是没有权限访问要走的对象
http.exceptionHandling().accessDeniedHandler(deniedHandler);
AuthenticationFailureHandler 用来解决登录失败的异常
package com.tyhxzy.springsecurity.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
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.util.HashMap;
import java.util.Map;
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException, IOException {
Map<String, Object> result = new HashMap<String, Object>();
result.put("msg", "登录失败: "+exception.getMessage());
result.put("status", 500);
response.setContentType("application/json;charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(result);
response.getWriter().println(s);
}
}
在WebSecurityConfig中添加如下代码:
@Autowired
@Autowired
private MyAuthenticationFailureHandler aa;
http.csrf().disable()
//表单登录
.formLogin()
//登录访问路径,与页面表单提交路径一致
.loginProcessingUrl("/login").failureHandler(aa)
五、常见的验证授权流程
一、Session-Cookie 机制 (web应用中最常见的)
当服务端需要对访问的客户端进行身份认证时,常用的做法是通过session-cookie 机制流程
Session-Cookie 认证存在的问题:
当客户访问量增加,服务端需要存储大量的session会话,对服务端有很大考验
当服务端为集群时,用户登录其中一台服务器,会将session保存在该服务器的内存中,
但是当用户访问其他服务器时。会无法访问。(已经有了成熟的解决方案)可以采用使用缓存服务器来保证共享 第三方缓存来保存session由于依赖cookie,所以存在CSRF安全问题
二、Token 认证机制:
使用Jwt
标签:Spring,用户,springframework,认证,import,org,Security,security From: https://www.cnblogs.com/YxinHaaa/p/17583550.html