首页 > 其他分享 >Spring Security

Spring Security

时间:2023-07-26 21:11:15浏览次数:30  
标签:Spring 用户 springframework 认证 import org Security security

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

image-20230726092135439

image-20230726092231898

启动项目,控制台中会打印出来密码:

image-20230726092246996

我们在浏览器中输入http://localhost:8080/user/list

直接跳转到http://localhost:8080/login

image-20230726092310004

输入完毕后点击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启动的时候会帮我们配置上。

image-20230726092906557

执行流程

image-20230726111837171

文字描述话术:
具体的执行流程其实是一个过滤链:

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>

五张表

image-20230726093119359

登录验证的是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 机制流程

image-20230726093141123

Session-Cookie 认证存在的问题:

当客户访问量增加,服务端需要存储大量的session会话,对服务端有很大考验
当服务端为集群时,用户登录其中一台服务器,会将session保存在该服务器的内存中,
但是当用户访问其他服务器时。会无法访问。(已经有了成熟的解决方案)可以采用使用缓存服务器来保证共享 第三方缓存来保存session由于依赖cookie,所以存在CSRF安全问题

二、Token 认证机制:

使用Jwt

标签:Spring,用户,springframework,认证,import,org,Security,security
From: https://www.cnblogs.com/YxinHaaa/p/17583550.html

相关文章

  • @mapper(componentModel = “spring”)
    在接口上使用该注解,可以自动生成该接口的实现类.实现DTO-DO各种模型之间的字段映射(不仅仅限制于DTO-DO)https://blog.csdn.net/qq_36937844/article/details/126848404......
  • 使用Memcached、Spring AOP构建数据库前端缓存框架
     上回说到Memcahed的安装及java客户端的使用(http://my249645546.iteye.com/blog/1420061),现在我们使用memcached、SpringAOP技术来构建一个数据库的缓存框架。数据库访问可能是很多网站的瓶颈。动不动就连接池耗尽、内存溢出等。前面已经讲到如果我们的网站是一个分布式的大型站......
  • Spring事务的传播行为
    Spring事务的七种传播行为首先举例事务的嵌套:ServiceA{voidmethodA(){ServiceB.methodB();}}ServiceB{voidmethodB(){}}其中ServiceA#methodA(我们称之为外部事务),ServiceB#methodB(我们称之为内部事务)......
  • 【Java面试题】Spring是如何解决循环依赖问题?
    ......
  • Spring 中的 @Cacheable 缓存注解,太好用了!
    1什么是缓存第一个问题,首先要搞明白什么是缓存,缓存的意义是什么。对于普通业务,如果要查询一个数据,一般直接select数据库进行查找。但是在高流量的情况下,直接查找数据库就会成为性能的瓶颈。因为数据库查找的流程是先要从磁盘拿到数据,再刷新到内存,再返回数据。磁盘相比于内存来......
  • SpringBoot中定时任务开启多线程避免多任务堵塞
    场景SpringBoot中定时任务与异步定时任务的实现:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/117083609使用SpringBoot原生方式实现定时任务,已经开启多线程支持,以上是方式之一。除此之外还可通过如下方式。为什么SpringBoot定时任务是单线程的?查看注解@Ena......
  • 一次性打包学透 Spring
    不知从何时开始,Spring这个词开始频繁地出现在Java服务端开发者的日常工作中,很多Java开发者从工作的第一天开始就在使用SpringFramework,甚至有人调侃“不会Spring都不好意思自称是个Java开发者”。之所以出现这种局面,源于Spring是一个极为优秀的一站式集成框架,对Java......
  • Spring Boot 实现文件断点下载,实战来了!
    来源:juejin.cn/post/7026372482110079012前言互联网的连接速度慢且不稳定,有可能由于网络故障导致断开连接。在客户端下载一个大对象时,因网络断开导致上传下载失败的概率就会变得不可忽视。客户端在GET对象请求时通过设置Range头部来告诉接口服务需要从什么位置开始输出对象......
  • spring启动流程 (6完结) springmvc启动流程
    SpringMVC的启动入口在SpringServletContainerInitializer类,它是ServletContainerInitializer实现类(Servlet3.0新特性)。在实现方法中使用WebApplicationInitializer创建ApplicationContext、创建注册DispatcherServlet、初始化ApplicationContext等。SpringMVC已经将大部分的启......
  • 你真正了解Spring的工作原理吗
     Spring  1.1什么是SpringIOC和DI?  ①控制反转(IOC):Spring容器使用了工厂模式为我们创建了所需要的对象,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。②依赖注入(DI):Spring使用JavaBean对象的Set方法或者带参数的构造方法......