SpringSecurity深度解析与实践(2)的网址
Spring Security 中的授权分为两种类型:
Spring Security 提供了多种实现授权的机制,最常用的是使用基于注解的方式,建立起访问资源和权限之间的映射关系。
其中最常用的两个注解是 @Secured
和 @PreAuthorize
。@Secured
注解是更早的注解,基于角色的授权比较适用,@PreAuthorize
基于 SpEL
表达式的方式,可灵活定义所需的权限,通常用于基于资源的授权。
WebSecurityConfig配置类
package com.yuan.springsecurity1.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuan.springsecurity1.resp.JsonResponseBody;
import com.yuan.springsecurity1.resp.JsonResponseStatus;
import com.yuan.springsecurity1.service.impl.UserServiceImpl;
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.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
//开启SpringSecurity的默认行为
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private DataSource dataSource;
/**
* 配置持久化Token方式,注意tokenRepository.setCreateTableOnStartup()配置
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 设置为true要保障数据库该表不存在,不然会报异常哦
// 所以第二次打开服务器应用程序的时候得把它设为false
tokenRepository.setCreateTableOnStartup(false);
return tokenRepository;
}
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
ObjectMapper objectMapper;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
/**
* 获取AuthenticationManager(认证管理器),登录时认证使用(基于数据库方式)
* @param
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager() throws Exception {
//创建DaoAuthenticationProvider
DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//设置userDetailsService,基于数据库方式进行身份认证
provider.setUserDetailsService(myUserDetailsService);
//配置密码编码器
provider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(provider);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception{
http.authorizeRequests()
// 开放接口访问权限,不需要登录就可以访问
.antMatchers("/toLogin").permitAll()
//访问路径有admin的方法时,需要有ADMIN的身份
// .antMatchers("/admin/**").hasRole("ADMIN")
// .antMatchers("/user/**").hasAnyRole("ADMIN","USER")
// 其余所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.formLogin()
// 设置登录页面的 URL
.loginPage("/toLogin")
// 设置登录请求的 URL,即表单提交的 URL
.loginProcessingUrl("/userLogin")
// 设置登录表单中用户名字段的参数名,默认为username
.usernameParameter("username")
// 设置登录表单中密码字段的参数名,默认为password
.passwordParameter("password")
//成功后的处理
.successHandler((req,resp,auth)->{
// resp.sendRedirect("/index");
Object user = auth.getPrincipal();
objectMapper.writeValue(resp.getOutputStream(), JsonResponseBody.success(user));
})
//登录失败的处理
.failureHandler(myAuthenticationFailureHandler)
.and()
.exceptionHandling()
// .accessDeniedPage("/noAccess")
//权限不够处理
.accessDeniedHandler((req,resp,ex)->{
objectMapper.writeValue(resp.getOutputStream(),JsonResponseBody.other(JsonResponseStatus.NO_ACCESS));
})
//没有认证(登录)处理
.authenticationEntryPoint((req,resp,ex)->{
objectMapper.writeValue(resp.getOutputStream(),JsonResponseBody.other(JsonResponseStatus.NO_LOGIN));
})
.and()
.logout()
// 设置安全退出的URL路径
.logoutUrl("/logout")
// 设置退出成功后跳转的路径
.logoutSuccessUrl("/toLogin")
.and()
.rememberMe()
// 指定 rememberMe 的参数名,用于在表单中携带 rememberMe 的值。
.rememberMeParameter("remember-me")
// 指定 rememberMe 的有效期,单位为秒,默认2周。
.tokenValiditySeconds(600)
// 指定 rememberMe 的 cookie 名称。
.rememberMeCookieName("remember-me-cookie")
// 指定 rememberMe 的 token 存储方式,可以使用默认的 PersistentTokenRepository 或自定义的实现。
.tokenRepository(persistentTokenRepository())
// 指定 rememberMe 的认证方式,需要实现 UserDetailsService 接口,并在其中查询用户信息。
.userDetailsService(myUserDetailsService);
// http.csrf().disable();
return http.build();
}
}
登录权限绑定
package com.yuan.springsecurity1.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.yuan.springsecurity1.pojo.*;
import com.yuan.springsecurity1.mapper.UserMapper;
import com.yuan.springsecurity1.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
*
* 用户信息表 服务实现类
*
*
* @author yuan
* @since 2023-12-21
*/
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private IUserService iUserService;
@Autowired
private IUserRoleService iUserRoleService;
@Autowired
private IRoleService iRoleService;
@Autowired
private IRoleModuleService iRoleModuleService;
@Autowired
private IModuleService iModuleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = iUserService.getOne(new QueryWrapper<User>().eq("username", username));
if(user==null)
throw new UsernameNotFoundException("用户名无效");
//查询所有的身份,map 遍历数据对象,返回的数据放到一个新的流中,collect(Collectors.toList())用list集合接收
List<Integer> userIds = iUserRoleService.list(new QueryWrapper<UserRole>().eq("user_id", user.getId())).stream().map(UserRole::getRoleId)
.collect(Collectors.toList());
//根据role_id拿到身份名称集合
List<String> roleNames = iRoleService.list(new QueryWrapper<Role>().in("role_id", userIds)).stream().map(Role::getRoleName).collect(Collectors.toList());
//根据身份id role_id拿到权限id module_id
List<Integer> moduleIds = iRoleModuleService.list(new QueryWrapper<RoleModule>().in("role_id", userIds)).stream().map(RoleModule::getModuleId).collect(Collectors.toList());
//根据权限id拿到对应的url
List<String> urls = iModuleService.list(new QueryWrapper<Module>().in("id", moduleIds)).stream().map(Module::getUrl).filter(Objects::nonNull).collect(Collectors.toList());
roleNames.addAll(urls);
List<SimpleGrantedAuthority> authorities = roleNames.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
user.setAuthorities(authorities);
return user;
}
}
Controller类数据
@ResponseBody
@RequestMapping("/o_f")
@PreAuthorize("hasAnyAuthority('order:manager:analysis')")
public String o_f() {
return "订单分析";
}
@ResponseBody
@RequestMapping("/b_add")
@PreAuthorize("hasAnyAuthority('book:manager:add')")
public String b_add() {
return "书籍新增";
}
需要添加一个规则,判断是否有权限,我这个放在WebSecurityConfig类上
@EnableGlobalMethodSecurity(prePostEnabled = true)
搭配WebSecurityConfig类中失败的对象
package com.yuan.springsecurity1.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuan.springsecurity1.pojo.User;
import com.yuan.springsecurity1.resp.JsonResponseBody;
import com.yuan.springsecurity1.resp.JsonResponseStatus;
import com.yuan.springsecurity1.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* @author 叶秋
* @site
* @company 卓京公司
* @create 2023-12-23 23:21
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
ObjectMapper objectMapper;
@Autowired
private IUserService iUserService;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if(1==2){ //假设超过三次失败
User user = iUserService.getOne(new QueryWrapper<User>().eq("username", request.getParameter("username")));
user.setAccountNonLocked(false);
iUserService.updateById(user);
}
objectMapper.writeValue(response.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE));
}
}