首页 > 其他分享 >整合spring security自定义认证

整合spring security自定义认证

时间:2023-10-28 21:46:14浏览次数:33  
标签:自定义 spring request throws token sysUser new security public

一、认证

1. 自定义组件

  (1)UserDetails自定义,实现用户登录方法;

public interface UserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService {
    /**
     * 根据用户名获取用户对象(获取不到直接抛异常)
     */
    @Override
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser sysUser = sysUserService.getByUsername(username);
        if(null == sysUser) {
            throw new UsernameNotFoundException("用户名不存在!");
        }

        if(sysUser.getStatus().intValue() == 0) {
            throw new RuntimeException("账号已停用");
        }

        return new CustomUser(sysUser, Collections.emptyList());
    }
}

  (2)自定义passwordEncoder

@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}

  (3)添加CustomUser对象

import com.lewang.model.system.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

/**
 * @Description :
 * @date :2023/10/26 21:06
 */
public class CustomUser extends User {

    /**
     * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
     */
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

    public SysUser getSysUser() {
        return sysUser;
    }

    public void setSysUser(SysUser sysUser) {
        this.sysUser = sysUser;
    }
}

 

2. 具体核心组件

  (1)登录filter,判断用户名和密码是否正确,生成token;

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    //构造方法
    public TokenLoginFilter(AuthenticationManager authenticationManager){
        this.setAuthenticationManager(authenticationManager);
        this.setPostOnly(false);
        //指定登录接口提交方式
        this.setRequiresAuthenticationRequestMatcher(
                new AntPathRequestMatcher("/admin/system/index/login","POST"));
    }

    //登录认证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
            //调用方式认证
            return this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //登录成功
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //获取用户
        CustomUser customUser = (CustomUser) authResult.getPrincipal();
        //生成token
        String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());
        //返回
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        ResponseUtil.out(response, Result.ok(map));
    }

    //登录失败
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtil.out(response,Result.build(null, ResultCodeEnum.LOGIN_ERROR));
    }
}

  (2)认证解析token

  从请求头中获取token,解析token获取用户信息,保存到上下文对象中

public class TokenAuthenticationFilter extends OncePerRequestFilter {

    public TokenAuthenticationFilter(){}

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //如果是登录接口,直接放行
        if("/admin/system/index/login".equals(request.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
        if (authentication != null){
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request,response);
        }else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
        }
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        //从请求头中获取token
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)){
            String username = JwtHelper.getUsername(token);
            if (!StringUtils.isEmpty(username)){
                return new UsernamePasswordAuthenticationToken(username,null, Collections.emptyList());
            }
        }
        return null;
    }
}

  (3)配置用户认证

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private CustomMd5PasswordEncoder customMd5PasswordEncoder;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
        http
                //关闭csrf跨站请求伪造
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                //TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
                .addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new TokenLoginFilter(authenticationManager()));

        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(customMd5PasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
    }

}

  当没有登录时访问接口会提示认证失败

  认证成功后返回token

 

二、授权

  (1)在获取用户信息时同时获取用户权限信息

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysMenuService sysMenuService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser sysUser = sysUserService.getByUsername(username);
        if(null == sysUser) {
            throw new UsernameNotFoundException("用户名不存在!");
        }

        if(sysUser.getStatus().intValue() == 0) {
            throw new RuntimeException("账号已停用");
        }

        //根据userid查询用户操作权限数据
        List<String> userPerms = sysMenuService.findUserPermsByUserId(sysUser.getId());
        //创建list集合,封装权限数据
        List<SimpleGrantedAuthority> authList = new ArrayList<>();
        //查询遍历集合
        for(String perms : userPerms){
            authList.add(new SimpleGrantedAuthority(perms.trim()));
        }
        return new CustomUser(sysUser, authList);
    }
}

  (2)修改TokenLoginFilter,增加权限数据;获取当前登录用户的权限数据,把权限数据保存到redis中

    //登录成功
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //获取用户
        CustomUser customUser = (CustomUser) authResult.getPrincipal();
        //生成token
        String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());

        //获取用户权限信息保存redis中
        redisTemplate.opsForValue().set(customUser.getUsername(), JSON.toJSONString(customUser.getAuthorities()));

        //返回
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        ResponseUtil.out(response, Result.ok(map));
    }

  (3)修改TokenAuthenticationFilter,认证从redis中获取权限数据

public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //如果是登录接口,直接放行
        if("/admin/system/index/login".equals(request.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
        if (authentication != null){
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request,response);
        }else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
        }
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        //从请求头中获取token
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)){
            String username = JwtHelper.getUsername(token);
            if (!StringUtils.isEmpty(username)){
                //使用username从redis中获取权限数据
                String authString = (String) redisTemplate.opsForValue().get(username);
                //把从redis中获取的权限字符串转换成数据权限集合类型List<SimpleGrantedAuthority>
                if (!StringUtils.isEmpty(authString)){
                    List<Map> mapList = JSON.parseArray(authString, Map.class);
                    System.out.println(mapList);
                    List<SimpleGrantedAuthority> authList = new ArrayList<>();
                    for(Map map : mapList){
                        String authority = (String) map.get("authority");
                        authList.add(new SimpleGrantedAuthority(authority));
                    }
                    return new UsernamePasswordAuthenticationToken(username,null, authList);
                }else {
                    return new UsernamePasswordAuthenticationToken(username,null, Collections.emptyList());
                }
            }
        }
        return null;
    }
}

  (4)修改配置类

  使用注解开启方法的认证机制

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法的权限控制
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CustomMd5PasswordEncoder customMd5PasswordEncoder;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
        http
                //关闭csrf跨站请求伪造
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                //TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
                .addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new TokenLoginFilter(authenticationManager(),redisTemplate));

        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(customMd5PasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
    }

}

  (5)在接口上添加权限注解

@ApiOperation("添加角色")
    @PostMapping("save")
    @PreAuthorize("hasAuthority('bnt.sysRole.add')")
    public Result save(@RequestBody SysRole role) {
        //调用service的方法
        boolean is_success = sysRoleService.save(role);
        if(is_success) {
            return Result.ok();
        } else {
            return Result.fail();
        }
    }

 

标签:自定义,spring,request,throws,token,sysUser,new,security,public
From: https://www.cnblogs.com/homle/p/17794289.html

相关文章

  • springMVC controller控制器方法HttpServletRequest等参数的是谁传递进来的
    SpringMVC中两个重要的接口:请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolver和HandlerMethodReturnValueHandlerHandlerMethodArgumentResolver的实现类 ServletRequestMethodArgumentResolver 参数类型是实现或继承或是WebRequest、ServletRequest......
  • vue 自定义指令
    v-if="yes"if就是指令ID,yes是expressionVue.directive(id,definition)接入两个参数,id是指令ID,definition是定义对象。定义对象可以提供一些钩子函数:bind:初始化的时候绑定inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)update:数据更新的时候......
  • 一些研发工程师在Springboot注意点
    一些研发工程师在Springboot注意点1.正确设计代码目录结构虽然您有很大的自由度,但有一些基本规则值得遵循来设计您的源代码结构。避免使用默认包。确保所有内容(包括入口点)都在命名良好的包中,这样您就可以避免与组装和组件扫描相关的意外情况;将Application.java(应用程序的入口类)......
  • 为什么Spring和IDEA不推荐使用@Autowired注解,有哪些替代方案?
    引言在使用Spring框架和JetBrainsIDEA集成开发环境(IDE)进行Java开发时,你可能经常会遇到@Autowired注解。@Autowired是Spring框架中用于实现依赖注入的核心注解之一。然而,近年来,Spring和IDEA都不再推荐使用@Autowired注解,并提出了更好的替代方案。本文将详细分析为什么Spring和IDE......
  • SpringBoot 自定义注解实现过程
    1、新建SpringBoot-Test 其中pom.xml文件如下:<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schem......
  • 逆向招商银行模拟器app,自定义修改任何元素,详细教程
    我今天闲着没事,就从网上找来了一个破解版的招商银行模拟器,然后这个APP呢是破解版,我们在给它继续完善优化一下吧。 因为这个版本存在众多问题,打开后会提示出来一个作者附加的信息,我下面给大家截图。出现这种提示非常麻烦,我这边要弄的通过逆向的办法把这个提示直接删除掉或者......
  • 自定义中间件 middleware
    文件夹的创建1、在项目或者应用下创建一个任意名称的文件夹2、在该文件夹下创建一个任意名称的py文件3、在该py文件中写自定义的中间件(这个类必须继承MiddlewareMixin)然后在这个类里面就可以自定义五个方法了 (这五个方法并不是全部都需要书写,用几个写几个)中间件......
  • FreeSWITCH添加自定义endpoint之api及app开发
    操作系统:CentOS7.6_x64FreeSWITCH版本:1.10.9之前写过FreeSWITCH添加自定义endpoint的文章,今天整理下api及app开发的笔记。历史文章可参考如下链接:FreeSWITCH添加自定义endpointFreeSWITCH添加自定义endpoint之媒体交互一、常用函数介绍这里列举下开发过程中常用的函数。1......
  • c# winfom从0学习开发OA、BPM工作流程与自定义表单系统(十三)新建工作
     1,新建一个工作 2,填写表单 3,当时表单设置的可写字段,在这里就体现出来了,这里设置的第一个节点开始是字段全部可写的,其他节点只能看不能写 4,转交下一步工作,也就是我们流程图连线箭头指向的节点 5,确定转交下一步工作 6,需要登录张三的账号,去代办工作里查看有没有需......
  • 详解SpringBoot @Conditional相关条件注解
    Springboot条件注解是@ContionalXXX相关的注解,表示当特定条件有效时,被修饰的配置类或配置方法才会生效。条件注解可以用来修饰@Configuration类或@Bean方法等。主要有以下行为:当SpringBoot检测到类加载路径包含某个框架时,会自动配置该框架的基础Bean.只有当开发者没配置某......