首页 > 其他分享 >spring-security

spring-security

时间:2022-11-23 17:56:49浏览次数:32  
标签:return String spring class Override new security public

springSecurity设置用户名和密码

一.配置文件中设置

springl.security.user.name=xxx
spring.security.user.password=xxx

二.配置类中设置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //加密处理密码
        String encode = new BCryptPasswordEncoder().encode("213");
        auth.inMemoryAuthentication()
            .withUser("lucy")
            .password(new BCryptPasswordEncoder().encode("123"))
            .roles("role");
    }
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }
}

三.自定义编写实现类

  1. 引入依赖

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-security</artifactId>
    </dependency>	
    
  2. 实现UserDetailService类重写loadUserByUsername(),在其中编写根据输入的用户名从数据库中获取密码的方法,并完善逻辑

    @Service(value="userDetailsService")//value可以不写,@service自动按照类型注入
    public class MyUserDetailService implements UserDetailsService {
        @Autowired
        SpringSecurityMapper springSecurityMapper;
    
        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            log.info("用户名是:{}",userName);
            LambdaQueryWrapper<SpringSecurity> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(!StringUtils.isEmpty(userName), SpringSecurity::getUserName, userName);
            SpringSecurity springSecurity = springSecurityMapper.selectOne(queryWrapper);
            if (springSecurity == null) {
                throw new  UsernameNotFoundException("用户名不存在");
            }
            List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE");
            return new User(springSecurity.getUserName(),springSecurity.getPassword(), auths);
        }
    }
    
  3. 编写配置类继承WebSecurityConfigurerAdapter,并设置密码加密规则

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
        /**
         * @Description 设置密码加密规则,并对密码进行加密和匹配
         * @return
         */
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
     
    }
    

springSecurity自定义认证功能

一.编写登陆功能的Controller

 @Autowired
    LoginService loginService;
    @PostMapping("/user/login")
    public Result login(@RequestBody UserLogin userLogin) throws Exception {
        Result result = loginService.login(userLogin);
        return result;
    }

二.在Service类中编写登陆认证功能

  1. 编写Controller接口,调用Service中的认证方法

  2. 在service方法中需要调用authenticationManager类,需要新建配置类,标注@EnableSecurity注解,并将AuthenticationConfiguration注入到Service中;

  3. 调用AuthenticationConfiguration的getAuthenticationManager()方法获取authenticationManager对象,并调用authenticationManager的authenticate()方法进行验证,该方法需要一个UsernamePasswordAuthenticationToken对象,将接口中传入的用户的用户名、密码、权限等信息封装到该对象中并传入方法;

  4. authenticationManager的authenticate()方法会调用自定义的实现了UserDetailService中的lodaUserByUserName()方法来进行用户验证

  5. 自定义MyUserDetailsService实现UserDetailsService接口,重写loadUserByUserName()方法,该方法会接收之前封装在UserNamePasswordAuthenticationToken对象中的用户名信息,然后再该方法中通过用户名信息去查询数据库中存储的用户信息

  6. 在loadUserByUserName()方法中查询到用户信息后,检查这个用户信息是否存在,如果不存在抛出异常(自定义逻辑),如果存在将查询到的用户信息封装到一个实现了UserDetails接口的类中,并将该类返回,该类需要重写UserDetails接口中的方法

  7. 方法经过其他逻辑后会再次返回LoginService实现类中的authenticationManager的authenticate()方法

  8. authenticationManager的authenticate()方法会返回一个Authentication对象,如果该对象为空,则登陆失败,否则其中的principal属性即使我们的loadUserByUserName()方法返回的实现了UserDetails接口对象,对象中封装了我们从数据库中查询到的完整信息,我们将该Principal对象强转为我们实现了UserDetails接口的对象,就可以调用相关的方法获取该对象的属性信息

  9. 根据返回的对象的属性信息返回JWT信息

Service登陆认证方法代码

//LoginService-------------------------------------------------------------------------
public interface LoginService {
    Result login(UserLogin userLogin) throws Exception;
}
//LoginServiceImpl----------------------------------------------------------------------
@Service
public class LoginServiceImpl  implements LoginService {
    @Autowired
    AuthenticationConfiguration authenticationConfiguration;
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public Result login(UserLogin userLogin) throws Exception {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLogin.getUserName(), userLogin.getPassword());
        //AuthenticationManager authenticate 进行用户认证
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //如果认证没有通过,给出对应的提示
        if (Objects.isNull(authenticate)){
            throw  new RuntimeException("登陆失败");
        }
        //如果认证ton过了,使用userid生成一个jwt
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUserLogin().getId().toString();
        HashMap<String, Object> map = new HashMap<>();
        map.put("userId", userId);
        String jwt = JWTUtil.setHS256JWT(map, "test");
        HashMap<String, Object> result = new HashMap<>();
        result.put("token", jwt);
        //把完整的用户信息存入Redis中,userid作为key
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("login"+userId,loginUser);
        return Result.ok(result);
    }
}

重写了UserDetailsService接口的方法的代码

@Service
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("用户名是:{}",username);
        LambdaQueryWrapper<UserLogin> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq( UserLogin::getUserName, username);
        UserLogin user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }else {
            return new LoginUser(user);
        }
    }

自定义实现了UserDetails接口的pojo类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
    //注入实体类
    private UserLogin userLogin;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return userLogin.getPassword();
    }

    @Override
    public String getUsername() {
        return userLogin.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true ;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * @Description 设置密码加密规则,并对密码进行加密和匹配
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()//关闭csrf
            //禁止session功能
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
            //对于登陆接口,允许匿名访问
                .antMatchers("/user/login").anonymous()
                .anyRequest().authenticated();//除了上面的路径,其他路径都需要认证
    }
}

配置springSecurityJWT过滤器

一.自定义filter类

@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取Token
        String token = request.getHeader("token");
        //如果token为空,放行(放行后springSecurity后续会处理异常)
        if (!StringUtils.hasText(token)) {
            filterChain.doFilter(request,response);
            return;
        }
        //解析Token获取其中的userId
        Claims test = JWTUtil.parseJWT(token);
        String userId = (String) test.get("userId");
        //获取Redis中存入的用户信息
        LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get("login" + userId);
        if (Objects.isNull(loginUser)) {
            throw new RuntimeException("用户为登陆");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到auhtenticationToken信息中
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request,response);
    }
}

二.在配置类中注入并配置JWT过滤器

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/user/login").anonymous()
                .anyRequest().authenticated();
        //过滤器配置
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }

权限系统

一.在security的配置类上添加注解

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//权限功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    JWTFilter jwtFilter;
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //关闭session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/login").anonymous()//登陆之后不许再次访问
                .anyRequest().authenticated();//其他请求都必须进行登陆认证
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

二.为接口资源添加所需权限

@GetMapping("/test")
    @PreAuthorize("hasAuthority('system:dept:list')")
    public String test(){
        return "hello";
    }

三.完善UserDetails接口的实现类,并查询后端返回权限集合

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
    private User user;
    //权限列表
    private List<String> permission;
    //忽略序列化,redis为了安全不存储SimpleGrantedAuthority类,让其不序列化
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    public LoginUser(User user, List<String> permission) {
        this.user = user;
        this.permission = permission;
    }
	
    /**
     * @Description 获取权限的方法,需要返回泛型为GrantedAuthority的实现类的值,这里为SimpleGrantedAuthority
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //如果不是第一次,authorities不为空直接返回
        if (authorities != null) {
            return authorities;
        }
        //如果是第一次,authorities为空,将通过构造器传入的权限列表转换为泛型为SimpleGrantedAuthority的权限列表
        authorities = permission.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }

    public String getNickName(){
        return user.getNickName();
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserService userService;
    @Autowired
    MenuMapper menuMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //从数据库中查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);
        User user = userService.getOne(queryWrapper);
        if (ObjectUtil.isEmpty(user)){
            throw  new UsernameNotFoundException("用户昵称不存在");
        }
        List<String> list = menuMapper.selectPermsByUserId(user.getId());
        return new LoginUser(user,list);
    }
}

四.在过滤器中封装权限信息

@Component
public class JWTFilter extends OncePerRequestFilter {
    @Autowired
    UserService userService;
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");
        //如果请求头中没有token信息,放行交给security后续过滤器处理异常信息
        if (!StringUtils.hasText(token)) {
            filterChain.doFilter(request, response);
            return;
        }
        //如果有token信息将token信息中的用户信息存在SecurityContextHolder中
        Claims claims = JWTUtil.parseJWT(token);
        String userId = (String) claims.get("userId");
        LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get("login:" + userId);
        if (ObjectUtil.isEmpty(loginUser)) {
            throw new RuntimeException("请重新登陆");
        }
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        //用户存在则将用户信息存入SecurityContextHolder中
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}

自定义异常

工具类

package com.epidemicprevent.system.utils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Queue;

public class WebUtils {
    public static String renderString(HttpServletResponse response, String string) {
        try {
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(string);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

自定义实现类

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Result result = Result.forbiiden("权限不足");
        String string = JSON.toJSONString(result);
        WebUtils.renderString(response, string);
    }
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Result result = Result.unAuthorized("登陆失败");
        String string = JSON.toJSONString(result);
        WebUtils.renderString(response, string);
    }
}

在SecurityConfig中添加配置

        //配置处理器
        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)//权限不足处理器
                .authenticationEntryPoint(authenticationEntryPoint);//认证失败处理器

跨域请求

springboot开启跨域

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //设置循序跨域的路径
        registry.addMapping("/**")
                //设置允许跨域请求的域名
                .allowedOrigins("*")
                //是否允许cookie
                .allowCredentials(true)
                //设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                //设置允许的请求头
                .allowedHeaders("*")
                //跨域允许的时间
                .maxAge(3600);
    }
}

springSecurity开启跨域

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    JWTFilter jwtFilter;
    @Autowired
    AccessDeniedHandler accessDeniedHandler;
    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //关闭session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/login").anonymous()//登陆之后不许再次访问
                .anyRequest().authenticated();//其他请求都必须进行登陆认证
        //配置JWT过滤器
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        //配置处理器
        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)//权限不足处理器
                .authenticationEntryPoint(authenticationEntryPoint);//认证失败处理器
        http.cors();//关闭跨域问题
    }
}

自定义权限验证

一.自定义实现类比编写权限验证逻辑

@Component("ex")//指定容器名称
public class YFExpression {
    public Boolean hasAuthority(String authority){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser principal = (LoginUser) authentication.getPrincipal();
        List<String> permission = principal.getPermission();
        return permission.contains(authority);
    }
}

二.在@preAuhtorize注解中调用我们自己的方法

    @GetMapping("/test")
    @PreAuthorize("@ex.hasAuthority('system:dept:list')")
    public String test(){
        return "hello";
    }

标签:return,String,spring,class,Override,new,security,public
From: https://www.cnblogs.com/yufou/p/16919205.html

相关文章

  • springBoot
    说说你对mvc的理解 MVC是一种设计模式,在这种设计模式下软件被分为了3层:model、view、controller;model层主要是定义实体对象等数据,封装了数据和对数据的操作,是实际进行数据......
  • SpringSecurity登录时报错栈溢出,StackOverflow,SpringSecurity多端登录实现方案
    最近在用springsecurity做一套医疗项目,要求一套后端对应两套前端界面,用户(患者)和医生。先写的用户登录界面,没有问题,再用同样方法写医生登录的时候报错栈溢出stackoverfl......
  • Spring 数据转换器
    版本2.7.61.依赖关系由于各个Spring数据模块的开始日期不同,因此它们中的大多数都带有不同的主要和次要版本号。找到兼容版本的最简单方法是依靠我们随附的与定义的兼容......
  • Spring Boot 多数据源配置
    第一种方式:AbstractRoutingDataSource1.1.手动切换数据源application.properties#Order#如果用Druid作为数据源,应该用url属性,而不是jdbc-urlspring.datasource.o......
  • java web开发(第一个spring程序)
        提到javaweb编程,好像spring就躲不开了。一般认为,spring有两个特征,分别是ioc、aop。两个英文单词的中文解释都比较拗口,一个称之为控制反转,一个是面向切面。对于......
  • Spring Cache + Redis;用Spring Cache的注解自动管理Redis缓存
    https://blog.csdn.net/qq_45839663/article/details/127209491?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHu......
  • springcloud -nacos-配置中心-接入
    1.nacaosClient接入:pom.xml引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator</artifactId>......
  • Spring Data(数据) Couchbase(二)
    5.4.7.仓库方法的空处理从SpringData2.0开始,返回单个聚合实例的存储库CRUD方法使用Java8来指示可能缺少值。除此之外,SpringData还支持在查询方法上返回以下包装器类......
  • 聊聊Spring中的数据绑定DataBinder
    数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容错性以及更高的编码效率。它提供的能力是:把字符串形式的参数转换成服......
  • Java基础__Spring思想
    IoC控制反转对象的创建控制权由程序转移到外部(解耦),Spring对IoC思想进行了实现Spring提供了一个容器,IoC容器,用于充当IoC思想的外部,被创建的对象在Io......