springboot+security

一、导入依赖

​ 先导入redis、JWT、SpringSecurity的相关依赖。

<!--redis-->
   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
<!--security-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
<!--JWT令牌-->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>

二、基于RBAC建表

​ RBAC 是基于角色的访问控制在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,
而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

三、建立User、Role、Perms实体类

1.用户类User
/**
*
* @author 淡薄愁雾
* @date 2024-02-04
*/

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable, UserDetails {
        private int id;
        private String account;
        @JsonIgnore  //由于访问时会进行Json互转会报错,密码加密所以需要忽略
        private String password;
        private Role role;
        @JsonIgnore
        @Override
        public Collection getAuthorities() {
                //将用户的角色权限信息封装成List
                List authorities = new ArrayList<>();
                //封装角色
                SimpleGrantedAuthority role = new SimpleGrantedAuthority(this.role.getName());
                authorities.add(role);
                //封装权限
                this.role.getPermsList().forEach(perms -> authorities.add(new SimpleGrantedAuthority(perms.getName())));
                return authorities;
        }
        @Override
        public String getUsername() {
                return account;
        }
        //账号是否没有过期
        @JsonIgnore
        @Override
        public boolean isAccountNonExpired() {
                return true;
        }
        //账号是否没有被锁定
        @JsonIgnore
        @Override
        public boolean isAccountNonLocked() {
                return true;
        }
        //凭证是否没有过期(密码)
        @JsonIgnore
        @Override
        public boolean isCredentialsNonExpired() {
                return true;
        }
        //账号是否可用
        @JsonIgnore
        @Override
        public boolean isEnabled() {
                return true;
        }
}
2.角色类Role
/**
*
* @author 淡薄愁雾
* @date 2024-02-04
*/
@Data
public class Role {
    private  Integer id;
    private  String name;
    @JsonIgnore
    private  String description;
    private List permsList;
}
3.权限类Perms
/**
 *
 * @author 淡薄愁雾
 * @date 2024-02-04
 */
@Data
public class Perms {
    private  Integer id;
    private  String name;
    private  String description;
}

四、编写工具类

​ 用于生成JWT令牌及常用Token常量

/**
 * @author 淡薄愁雾
 * @date 2024-02-04 21:50
 */
public class JWTUtil {
    public static final String SECRET_KEY = "123456"; //秘钥
    public static final long TOKEN_EXPIRE_TIME = 24 * 3600 * 1000L; //token过期时间
    public static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 3600 * 1000L; //refreshToken过期时间
    private static final String ISSUER = "issuer"; //签发人

    /**
     * 生成签名
     */
    public static String generateToken(int uid, String account){
        Date now = new Date();
        //创建签名算法对象
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); //算法

        String token = JWT.create()
                .withIssuer(ISSUER) //签发人
                .withIssuedAt(now)  //签发时间
                .withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME)) //过期时间
                .withClaim("uid", uid) //保存身份标识
                .withClaim("account", account) //保存身份标识
                .sign(algorithm);
        return token;
    }

    /**
     * 验证token
     */
    public static TokenEnum verify(String token){
        try {
            //签名算法
            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); //算法
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(ISSUER)
                    .build();
            verifier.verify(token);
            return TokenEnum.TOKEN_SUCCESS;
        } catch (TokenExpiredException ex){
            return TokenEnum.TOKEN_EXPIRE;
            //ex.printStackTrace();
        } catch (Exception e) {
            return TokenEnum.TOKEN_BAD;
        }
    }
    /**
     * 从token获取uid
     */
    public static int getuid(String token){
        try{
            return JWT.decode(token).getClaim("uid").asInt();
        }catch(Exception ex){
            ex.printStackTrace();
        }
        return 0;
    }
    public static String getAccount(String token){
        try{
            return JWT.decode(token).getClaim("account").asString();
        }catch(Exception ex){
            ex.printStackTrace();
        }
        return null;
    }
}

编写Token常量(可以直接写)

public enum TokenEnum {
    TOKEN_EXPIRE,TOKEN_BAD,TOKEN_SUCCESS
}

五、编写配置类

package com.smiao.vegetable.config;
import com.smiao.vegetable.exception.CustomAccessDeniedExceptionHandle;
import com.smiao.vegetable.filter.AuthFilter;
import com.smiao.vegetable.service.CustomUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.AuthenticationManager;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 开启security的注解权限管理
public class CustomSpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Resource
    private AuthFilter authFilter;
    /**
     * 配置密码编码器
     * NoOpPasswordEncoder: 不加密看需求
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
//        return NoOpPasswordEncoder.getInstance();
        return new BCryptPasswordEncoder();
    }
    /**
    *  重写方法:指定当前系统的用户信息来自哪里
    *  数据库,开发
    */
    @Resource
    private CustomUserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定用户信息来自数据库
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    /**
    * 配置那个uri能被那些身份访问
    *
    */
    @Resource
    private CustomAccessDeniedExceptionHandle customAccessDeniedExceptionHandle;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .antMatchers("/user/login","/images/**")
                .permitAll() // permitAll()不需要登录就能访问
                .anyRequest().authenticated()//其他的uri。必须要登录才能访问
                .and()
                .formLogin()    // 开启表单登录
                .and()
                .exceptionHandling()    //开启异常处理
                .accessDeniedHandler(customAccessDeniedExceptionHandle)
                .and()
                .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
                .csrf().disable();    //关闭csrf防御

    }

    // 创建认证管理器对象
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    // 配置编码过滤器,用户数据中如果有中文会报错
    String allowedPattern = ".*";

    @Bean
    public StrictHttpFirewall httpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowedHeaderValues((header) -> {
            String parsed = new String(header.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            return parsed.matches(allowedPattern);
        });
        return firewall;
    }

}

六、编写业务代码

package com.smiao.vegetable.controller;

import com.smiao.vegetable.entity.User;
import com.smiao.vegetable.response.ResponseResult;
import com.smiao.vegetable.service.UserService;
import com.smiao.vegetable.util.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
*
* @author one piece
* @date 2024-02-04 22:12
*/
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private RedisTemplate redisTemplate;
    @PostMapping("/login")
    public ResponseResult login(@RequestBody User user1, HttpServletResponse response){
        // 登录
        User result =  userService.login(user1);
        //进行密码比较
        boolean matches = new BCryptPasswordEncoder().matches(user1.getPassword(), result.getPassword());
        //密码相同,登录成功
        if(matches){
            // 生成token
            String token = JWTUtil.generateToken(result.getId(), result.getAccount());
            String refreshtoken = UUID.randomUUID().toString();
            // 放入redis并设置过期时间
            redisTemplate.opsForValue().set(refreshtoken, token, JWTUtil.REFRESH_TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
            // 返回给前端:通过自定义响应头的方式返回
            response.setHeader("Authorization", token);
            response.setHeader("RefreshToken", refreshtoken);
            // 暴露头:为了不让浏览器忽略这个头信息
            response.setHeader("Access-Control-Expose-Headers","Authorization,RefreshToken");
            return new ResponseResult<>(ResponseResult.SUCCESS,"success", result);
        }
        return new ResponseResult<>(ResponseResult.LOGIN_FAIL,"用户名或密码有误", null);
    }
}

Mapper

package com.smiao.vegetable.mapper;
import com.smiao.vegetable.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
 * @Author: one piece
 * @Description:
 * @Date: 2024/2/4 22:05
 */

@Mapper
public interface UserMapper {
    User findByAccount(String account);
}

其他的异常处理类、统一结果返回及前端代码我想大家都会
跨域问题
前后端分离项目中,后台SpringBoot整合SpringSecurity之后如果想要前台能正常访问后台接口需要开启SpringBoot和SpringSecurity的跨域访问

package com.smiao.vegetable.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CrossOriginFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletResponse res = (HttpServletResponse) response;
        res.addHeader("Access-Control-Allow-Credentials", "true");
        res.addHeader("Access-Control-Allow-Origin", "*");
        res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN ,Authorization ,Refreshtoken");

        if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
            response.getWriter().println("ok");
            return;
        }
        chain.doFilter(request, response);
	}
}

你可能感兴趣的:(springboot,spring,boot,java,后端,intellij-idea)