基于token的springsecurity前后端分离实现!

本文是在超全的springboot+springsecurity前后端分离简单实现!!!_码上编程的博客-CSDN博客 此文的基础上进一步改进的,

没有接触过springsecurity的可以先看看超全的springboot+springsecurity前后端分离简单实现!!!_码上编程的博客-CSDN博客,两篇文章都是每一步都有详细步骤!

1、前言部分

1.1 序言

我的上一篇博客超全的springboot+springsecurity前后端分离简单实现!!!_码上编程的博客-CSDN博客 虽然实现了前后端分离,但是它是基于cookie认证的,我想尝试一下不用cookie认证,而是用token认证。网上绝大部分都是使用 jwt(json web token)方式认证,而jwt加密和解密比较繁琐,现实当中好像用的挺少的,但是不可否认它更安全,如果想看基于jwt方式认证的,我推荐此博客SpringBoot整合SpringSecurity+JWT实现用户验证和鉴权_西瓜不甜柠檬不酸的博客-CSDN博客,但这篇还是使用token认证,将token保存在redis缓存中。

源代码还有数据库在此文的底部!!

1.2 目标实现效果

注册, 密码必须经过BCryptPasswordEncoder进行加密,权限必须以ROLE_开头才能被springsecurity识别

 

账号密码错误,登录失败 。 /login接口有springsecurity写好了,直接调用即可,但是必须使用post请求, 参数名必须是usernamepassword,要不然登录不成功。

登录成功 , 并在响应头中返回token

 

权限不足访问目标资源, /hello是我写的一个接口,返回"hello"字符串。

权限匹配访问目标资源, /index 是我写的一个接口,返回"index"字符串

注销操作, /logout是springsecurity自动封装的接口,无论是get请求还是post请求,直接调用即可, 注销成功后会删除缓存里的token

1.3 技术使用

springboot、mybatis-plus、redis、mysql、lombok自动生成get/set、git

2、关键部分

2.1 原理

2.2 代码部分

<dependencies>
        <!--转换成json字符串的工具-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>
        <!-- 配置使用redis启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
		<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>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!--mybatis-plus依赖-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.1</version>
		</dependency>
		<!--模板引擎-->
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-engine-core</artifactId>
			<version>2.2</version>
		</dependency>
		<!--自动生成代码时会用到的依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.4.2</version>
        </dependency>
    </dependencies>

User.java

@Data
@EqualsAndHashCode(callSuper = false)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private String role;


}

UserMapper.java, BaseMapper<T>封装了大量的sql语句供程序员使用

@Repository
public interface UserMapper extends BaseMapper<User> {

}

UserService.java

public interface UserService extends IService<User> {

}

Msg.java 自定义结果返回集

/**
 * 自定义结果集处理
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Msg {
    int code;
    String message;
    Object data;

    //无权访问
    public static Msg denyAccess(String message){
        Msg result=new Msg();
        result.setCode(300);
        result.setMessage(message);
        return result;
    }

    //操作成功
    public static Msg success(String message){
        Msg result=new Msg();
        result.setCode(200);
        result.setMessage(message);
        return result;
    }

    //客户端操作失败
    public static Msg fail(String message){
        Msg result=new Msg();
        result.setCode(400);
        result.setMessage(message);
        return result;
    }

}

注销处理器

@Component
public class AuthenticationLogout implements LogoutSuccessHandler {

    @Autowired
    Gson gson;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String token = request.getHeader("token");
        Msg result=null;
        try {
            if(token==null){
                //token为空表示未登录,注销失败
                result=Msg.fail("未登录,不能进行注销操作!!!");
            }else{
                String username = stringRedisTemplate.opsForValue().get(token);
                if(username==null){
                    //token不正确,注销失败
                    result=Msg.fail("登录凭证异常,注销失败!!!");
                }else{
                    //token正确,注销成功
                    result=Msg.success("注销成功");
                    //清空token
                    stringRedisTemplate.delete(token);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        response.setContentType("application/json;charset=utf-8");  //设置编码格式
        response.getWriter().write(gson.toJson(result));    //返回给前端
    }
}

未登录时处理器

/**
 * 未登录时处理器
 */
public class TokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
    Gson gson=new Gson();

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        Msg msg= Msg.fail("请登录!!");
        response.getWriter().write(gson.toJson(msg));
    }
}

权限不足处理器 

/**
 * 权限不足处理器
 */
public class TokenAccessDeniedHandler implements AccessDeniedHandler {

    Gson gson=new Gson();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        Msg msg= Msg.denyAccess("权限不够,请联系管理员!!!");
        response.getWriter().write(gson.toJson(msg));
    }
}

请求过滤器 , token没有或者不正确的时候, 告诉用户执行相应操作,token正确且未认真的情况下则放行请求, 交由认证过滤器进行认证操作

/**
 * 自定义请求过滤器,token没有或者不正确的时候,
 * 告诉用户执行相应操作,token正确且未认真的情况下则放行请求,
 * 交由认证过滤器进行认证操作
 */
public class OncePerRequestAuthoricationFilter extends BasicAuthenticationFilter {

    StringRedisTemplate stringRedisTemplate;
    UserServiceImpl userServiceImpl;

    Gson gson=new Gson();

    public OncePerRequestAuthoricationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate, UserServiceImpl userServiceImpl) {
        super(authenticationManager);
        this.stringRedisTemplate=stringRedisTemplate;
        this.userServiceImpl=userServiceImpl;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String token=request.getHeader("token");
        if(token==null || token.equals("")){
            //token为空,则返回空
            chain.doFilter(request, response);
            return;
        }

        String username=stringRedisTemplate.opsForValue().get(token);
        try{
            //判断token情况,给予对应的处理方案
            if(username==null){
                throw new Exception("登录凭证不正确或者超时了,请重新登录!!!");
            }else{
                UserDetails userDetails = userServiceImpl.loadUserByUsername(username);
                if(userDetails!=null){
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(username,null,userDetails.getAuthorities());
                    response.setHeader("token",token);
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            }
        }catch (Exception e){
            //抛出异常,并返回给前端
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            Msg msg= Msg.fail(e.getLocalizedMessage());
            response.getWriter().write(gson.toJson(msg));
            response.getWriter().flush();
            chain.doFilter(request, response);
            return;
        }
        super.doFilterInternal(request,response,chain);
    }
}

认证过滤器,   判断认证成功还是失败,并给予相对应的逻辑处理

/**
 * 自定义认证过滤器,判断认证成功还是失败,并给予相对应的逻辑处理
 */
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    AuthenticationManager authenticationManager;
    StringRedisTemplate stringRedisTemplate;

    Gson gson=new Gson();

    public AuthenticationFilter(AuthenticationManager authenticationManager,StringRedisTemplate stringRedisTemplate){
        this.authenticationManager=authenticationManager;
        this.stringRedisTemplate=stringRedisTemplate;
    }

    //未认证时调用此方法,判断认证是否成功,认证成功与否由authenticationManager.authenticate()去判断,我们在这里只负责传递所需要的参数即可
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username=request.getParameter("username");
        String password=request.getParameter("password");
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password,new ArrayList<>()));
    }

    //验证成功操作
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        /**
         * 验证成功则向redis缓存写入token,然后在响应头添加token,并向前端返回
         */
        String token= UUID.randomUUID().toString().replaceAll("-","");  //token本质就是随机生成的字符串
        stringRedisTemplate.opsForValue().set(token,request.getParameter("username"),60*10,TimeUnit.SECONDS);    //存入缓存中
        response.setHeader("token",token);  //在响应头添加token
        Msg msg= Msg.success("登录成功");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(gson.toJson(msg));
    }

    //验证失败
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        /**
         * 验证成功则向前端返回失败原因
         */
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        Msg msg=Msg.fail("账号或者密码错误");
        response.getWriter().write(gson.toJson(msg));
    }
}

springsecurity核心配置文件,无论是处理器还是过滤器都需要注入到此

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //java操作redis的string类型数据的类
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    //注销处理器
    @Autowired
    AuthenticationLogout authenticationLogout;

    //加密
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Bean
    public UserDetailsService userDetailsService() {
        return new UserServiceImpl();
    }

    /**
     * 认证
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
    }


    /**
     * 授权
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .anyRequest().permitAll()

                .and()
                .logout()
                .permitAll()
                .logoutSuccessHandler(authenticationLogout) //注销时的逻辑处理

                .and()
                .addFilter(new AuthenticationFilter(authenticationManager(),stringRedisTemplate))   //自定义认证过滤器
                .addFilter(new OncePerRequestAuthoricationFilter(authenticationManager(),stringRedisTemplate, (UserServiceImpl) userDetailsService())) //自定义请求过滤器
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)     //去除默认的session、cookie

                .and()
                .exceptionHandling().authenticationEntryPoint(new TokenAuthenticationEntryPoint())  //未登录时的逻辑处理
                .accessDeniedHandler(new TokenAccessDeniedHandler());    //权限不足时的逻辑处理
    }


    /**
     * 用于解决跨域问题
     * @return
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

 UserServiceImpl.java, 实现UserDetailsService里面的loadUserByUsername()方法,AuthenticationManager会调用此方法去获取用户数据信息,从而完成认证。

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService,UserDetailsService {
    @Autowired
    UserMapper userMapper;

    /**
     * 实现UserDetailsService接口的方法,用于获取用户个人信息
     * @param s
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //根据用户名查找用户,相当于select * from user where username='${s}'
        QueryWrapper<User> wrapper=new QueryWrapper<>();
        wrapper.eq("username",s);
        User user = userMapper.selectOne(wrapper);
        if(user==null){
            throw new UsernameNotFoundException("用户名错误!!");
        }

        //获取用户权限,并把其添加到GrantedAuthority中
        List<GrantedAuthority> grantedAuthorities=new ArrayList<>();
        GrantedAuthority grantedAuthority=new SimpleGrantedAuthority(user.getRole());
        grantedAuthorities.add(grantedAuthority);

        return new org.springframework.security.core.userdetails.User(s,user.getPassword(),grantedAuthorities);
    }

    /**
     * 注册操作
     * @param user
     * @return
     */
    public Msg register(User user){
        user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));  //对密码进行加密
        int insert = userMapper.insert(user);
        if(insert>0){
            return Msg.success("注册成功!");
        }else{
            return Msg.fail("注册失败!");
        }
    }
}
    

 UserController.java ,   @PreAuthorize("hasRole('ROLE_USER')")  指定接口拥有ROLE_USER的权限方可访问的注解。

@RestController
public class UserController {

    @Autowired
    UserServiceImpl userServiceImpl;

    /**
     * 注册操作
     * @param user
     * @return
     */
    @PostMapping("/register")
    public Msg register(User user){
        return userServiceImpl.register(user);
    }

    /**
     * 当权限为ROLE_ADMIN时方可访问,否则抛出权限不足异常
     * @return
     */
    @GetMapping("/index")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public Msg index(){
        Msg msg=Msg.success("查询成功!!!");
        msg.setData("index");
        return msg;
    }

    /**
     * 当权限为ROLE_USER时方可访问,否则抛出权限不足异常
     * @return
     */
    @GetMapping("/hello")
    @PreAuthorize("hasRole('ROLE_USER')")
    public Msg hello(){
        Msg msg=Msg.success("查询成功!!!");
        msg.setData("hello");
        return msg;
    }
}

项目源代码地址: https://gitee.com/liu-wenxin/springsecurity_token.git ,   通过 git clone https://gitee.com/liu-wenxin/springsecurity_token.git  获取源代码以及数据库文件。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值