目录
标签:Security.登录.权限;
一、简介
SpringSecurity组件可以为服务提供安全管理的能力,比如身份验证、授权和针对常见攻击的保护,是保护基于spring应用程序的事实上的标准;
在实际开发中,最常用的是登录验证和权限体系两大功能,在登录时完成身份的验证,加载相关信息和角色权限,在访问其他系统资源时,进行权限的验证,保护系统的安全;
二、工程搭建
1、工程结构

2、依赖管理
在starter-security依赖中,实际上是依赖spring-security组件的6.1.1版本,对于该框架的使用,主要是通过自定义配置类进行控制;- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- <version>${spring-boot.version}</version>
- </dependency>
复制代码 三、配置管理
1、核心配置类
在该类中涉及到的配置非常多,主要是服务的拦截控制,身份认证的处理流程以及过滤器等,很多自定义的处理类通过该配置进行加载;- @EnableWebSecurity
- @EnableMethodSecurity
- @Configuration
- public class SecurityConfig {
- /**
- * 基础配置
- */
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
- // 配置拦截规则
- httpSecurity.authorizeHttpRequests(authorizeHttpRequests->{
- authorizeHttpRequests
- .requestMatchers(WhiteConfig.whiteList()).permitAll()
- .anyRequest().authenticated();
- });
- // 禁用默认的登录和退出
- httpSecurity.formLogin(AbstractHttpConfigurer::disable);
- httpSecurity.logout(AbstractHttpConfigurer::disable);
- httpSecurity.csrf(AbstractHttpConfigurer::disable);
- // 异常时认证处理流程
- httpSecurity.exceptionHandling(exeConfig -> {
- exeConfig.authenticationEntryPoint(authenticationEntryPoint());
- });
- // 添加过滤器
- httpSecurity.addFilterAt(authTokenFilter(),CsrfFilter.class);
- return httpSecurity.build() ;
- }
- @Bean
- public BCryptPasswordEncoder passwordEncoder(){
- return new BCryptPasswordEncoder();
- }
- @Bean
- public AuthenticationEntryPoint authenticationEntryPoint() {
- return new AuthExeHandler();
- }
- @Bean
- public OncePerRequestFilter authTokenFilter () {
- return new AuthTokenFilter();
- }
- /**
- * 认证管理
- */
- @Bean
- public AuthenticationManager authenticationManager() {
- return new ProviderManager(authenticationProvider()) ;
- }
- /**
- * 自定义用户认证流
- */
- @Bean
- public AbstractUserDetailsAuthenticationProvider authenticationProvider() {
- return new AuthProvider() ;
- }
- }
复制代码 2、认证数据源
UserDetailsService是加载用户特定数据的核心接口,编写用户服务类并实现该接口,提供用户信息和权限体系的数据查询和加载,作为用户身份识别的关键凭据;- @Service
- public class UserService implements UserDetailsService {
- @Resource
- private UserBaseMapper userBaseMapper;
- @Resource
- private BCryptPasswordEncoder passwordEncoder;
- @Override
- public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
- UserBase queryUser = geyByUserName(userName);
- if (Objects.isNull(queryUser)){
- throw new AuthException("该用户不存在");
- }
- List<GrantedAuthority> grantedAuthorityList = new ArrayList<>() ;
- grantedAuthorityList.add(new SimpleGrantedAuthority(queryUser.getUserRole())) ;
- return new User(queryUser.getUserName(),queryUser.getPassWord(),grantedAuthorityList);
- }
- public int register (UserBase userBase){
- if (!Objects.isNull(userBase)){
- userBase.setPassWord(passwordEncoder.encode(userBase.getPassWord()));
- userBase.setCreateTime(new Date()) ;
- return userBaseMapper.insert(userBase) ;
- }
- return 0 ;
- }
- public UserBase getById (Integer id){
- return userBaseMapper.selectById(id) ;
- }
- public UserBase geyByUserName (String userName){
- List<UserBase> userBaseList = new LambdaQueryChainWrapper<>(userBaseMapper)
- .eq(UserBase::getUserName,userName).last("limit 1").list();
- if (userBaseList.size() > 0){
- return userBaseList.get(0) ;
- }
- return null ;
- }
- }
复制代码 3、认证流程
自定义用户名和密码的身份令牌认证逻辑,基于用户名Username从上面的用户服务类中加载数据并校验,在验证成功后将用户的身份令牌返回给调用者;- @Component
- public class AuthProvider extends AbstractUserDetailsAuthenticationProvider {
- private static final Logger log = LoggerFactory.getLogger(AuthProvider.class);
-
- @Resource
- private UserService userService;
- @Resource
- private BCryptPasswordEncoder passwordEncoder;
- @Override
- protected void additionalAuthenticationChecks(
- UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
- throws AuthenticationException {
- User user = (User) userDetails;
- String loginPassword = authentication.getCredentials().toString();
- log.info("user:{},loginPassword:{}",user.getPassword(),loginPassword);
- if (!passwordEncoder.matches(loginPassword, user.getPassword())) {
- throw new AuthException("账号或密码错误");
- }
- authentication.setDetails(user);
- }
- @Override
- protected UserDetails retrieveUser(
- String username, UsernamePasswordAuthenticationToken authentication)
- throws AuthenticationException {
- log.info("username:{}",username);
- return userService.loadUserByUsername(username);
- }
- }
复制代码 4、身份过滤器
通过继承OncePerRequestFilter抽象类,实现用户身份的过滤器,如果不是白名单请求,需要验证令牌是否正确有效,SecurityContextHolder默认状态下使用ThreadLocal存储信息;- @Component
- public class AuthTokenFilter extends OncePerRequestFilter {
- @Resource
- private AuthTokenService authTokenService ;
- @Resource
- private AuthExeHandler authExeHandler ;
- @Override
- protected void doFilterInternal(@Nonnull HttpServletRequest request,
- @Nonnull HttpServletResponse response,
- @Nonnull FilterChain filterChain) throws ServletException, IOException {
- String uri = request.getRequestURI();
- if (Arrays.asList(WhiteConfig.whiteList()).contains(uri)){
- // 如果是白名单直接放行
- filterChain.doFilter(request,response);
- } else {
- String token = request.getHeader("Auth-Token");
- if (Objects.isNull(token) || token.isEmpty()){
- // Token不存在,拦截返回
- authExeHandler.commence(request,response,null);
- } else {
- Object object = authTokenService.getToken(token);
- if (!Objects.isNull(object) && object instanceof User user){
- UsernamePasswordAuthenticationToken authentication =
- new UsernamePasswordAuthenticationToken(user, null,user.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authentication);
- filterChain.doFilter(request,response);
- } else {
- // Token验证失败,拦截返回
- authExeHandler.commence(request,response,null);
- }
- }
- }
- }
- }
复制代码 四、核心功能
1、登录退出
自定义登录和退出两个接口,基于用户名和密码执行上述的身份认证流程,如果认证成功则返回用户的身份令牌,在请求「非」白名单接口时需要在请求头中Auth-Token:token携带该令牌,在退出时会清除身份信息;- @Service
- public class LoginService {
- private static final Logger log = LoggerFactory.getLogger(LoginService.class);
- @Resource
- private AuthTokenService authTokenService ;
- @Resource
- private AuthenticationManager authenticationManager;
- public String doLogin (UserBase userBase){
- AbstractAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
- userBase.getUserName().trim(), userBase.getPassWord().trim());
- Authentication authentication = authenticationManager.authenticate(authToken) ;
- User user = (User) authentication.getDetails();
- return authTokenService.createToken(user) ;
- }
- public Boolean doLogout (String authToken){
- SecurityContextHolder.clearContext();
- return authTokenService.deleteToken(authToken) ;
- }
- }
- @Service
- public class AuthTokenService {
- private static final Logger log = LoggerFactory.getLogger(AuthTokenService.class);
- @Resource
- private RedisTemplate<String,Object> redisTemplate ;
- public String createToken (User user){
- String userName = user.getUsername();
- String token = DigestUtils.md5DigestAsHex(userName.getBytes());
- log.info("user-name:{},create-token:{}",userName,token);
- redisTemplate.opsForValue().set(token,user,10, TimeUnit.MINUTES);
- return token ;
- }
- public Object getToken (String token){
- return redisTemplate.opsForValue().get(token);
- }
- public Boolean deleteToken (String token){
- return redisTemplate.delete(token);
- }
- }
复制代码 2、权限校验
UserWeb类中提供用户的注册接口,在用户表中创建两个测试用户:admin对应ROLE_Admin角色,user对应ROLE_User角色,验证如下几个接口的权限控制;
select接口不需要鉴权,拦截器放行即可访问;getUser接口校验ROLE_User角色;getAdmin接口校验ROLE_Admin角色;query接口校验两个角色中的任意一个即可;
两个不同用户登录获取到各自的身份令牌,使用不同的令牌请求接口,在PreAuthorize验证通过后才可以正常访问;- @RestController
- public class UserWeb {
- @Resource
- private UserService userService ;
- @PostMapping("/register")
- public String register (@RequestBody UserBase userBase){
- return "register-"+userService.register(userBase) ;
- }
- @GetMapping("/select/{id}")
- public UserBase select (@PathVariable Integer id){
- return userService.getById(id) ;
- }
- @PreAuthorize("hasRole('User')")
- @GetMapping("/user/{id}")
- public UserBase getUser (@PathVariable Integer id){
- return userService.getById(id) ;
- }
- @PreAuthorize("hasRole('Admin')")
- @GetMapping("/admin/{id}")
- public UserBase getAdmin (@PathVariable Integer id){
- return userService.getById(id) ;
- }
- @PreAuthorize("hasAnyRole('User','Admin')")
- @GetMapping("/query/{id}")
- public UserBase query (@PathVariable Integer id){
- return userService.getById(id) ;
- }
- }
复制代码 五、参考源码
- 文档仓库:
- https://gitee.com/cicadasmile/butte-java-note
- 源码仓库:
- https://gitee.com/cicadasmile/butte-spring-parent
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |