项目搭建---SpringSecurity

项目搭建—SpringSecurity

每一步直接都是有联系的,若中途出现爆红,等所有配置结束后,就不会爆红了。

依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
			  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
  • 第一步 先创建 LoginUserUser 两个实体类,一个是用来验证登录的,一个是用来保存用户信息的。并创建 UserMapper
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true) //要加上这个注解,否则这个User的值从redis绑定过来的时候绑定不上了
public class LoginUser implements UserDetails {
    private User user;
    private List<String> permissions;
//    @JSONField(serialize = false)       //不让 List<SimpleGrantedAuthority> authorities 存入到redis,因为SimpleGrantedAuthority不适合序列化
    @JsonIgnore
    private List<GrantedAuthority> authorities;
    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities != null){
            return authorities;
        }
        //把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象
        authorities = new ArrayList<>();
        for(String permission : permissions){
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
            authorities.add(authority);
        }
        return null;
    }
    @Override
    public String getPassword() {
        return new BCryptPasswordEncoder().encode(user.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;
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class User implements Serializable{
    //用户id
    @JsonSerialize(using = ToStringSerializer.class)
    @TableId
    private Long id;

    //用户名 -- 邮箱
    private String username;

    //用户密码
    private String password;

    //身份证
    private String identityCard;

    //用户性别
    private String gender;

    //地理位置信息
    private String location;

    //用户头像
    private String userImage;

    //用户电话
    private String phoneNumber;

    //用户创建时间
    private String createTime;

    //用户修改时间
    private String updateTime;

    //逻辑删除
    @TableLogic
    private int isDelete;
}
@Repository
public interface UserMapper extends BaseMapper<User> {
}
  • 第二步 创建 Menu 菜单类用于管理角色权限,并创建 MenuMapper 用于查询用户权限,并创建 MenuMapper.xml
@TableName(value="sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {
    private static final long serialVersionUID = -54979041104113736L;

    @TableId
    private Long id;
    /**
     * 菜单名
     */
    private String menuName;
    /**
     * 路由地址
     */
    private String path;
    /**
     * 组件路径
     */
    private String component;
    /**
     * 菜单状态(0显示 1隐藏)
     */
    private String visible;
    /**
     * 菜单状态(0正常 1停用)
     */
    private String status;
    /**
     * 权限标识
     */
    private String perms;
    /**
     * 菜单图标
     */
    private String icon;

    private Long createBy;

    private Date createTime;

    private Long updateBy;

    private Date updateTime;
    /**
     * 是否删除(0未删除 1已删除)
     */
    private Integer delFlag;
    /**
     * 备注
     */
    private String remark;
}

@Repository
public interface MenuMapper extends BaseMapper<Menu> {

    public List<String> selectPermsByUserId(@Param("userId") Long userId);
    
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.neu.mapper.MenuMapper">

    <select id="selectPermsByUserId" resultType="java.lang.String">
        select
            DISTINCT m.`perms`
        FROM
            sys_user_role ur
                LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
            user_id = #{userId}
            AND r.`status` = 0
            AND m.`status` = 0
    </select>


</mapper>
  • 第三步 实现一个 UserDetailsServiceImpl 实现类
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    MenuMapper menuMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        User user = userMapper.selectOne(wrapper);
        //判断了
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        List<String> permissions = menuMapper.selectPermsByUserId(user.getId());
        //密码比对的那部分,SpringSecurity自动帮你做了
        return new LoginUser(user,permissions);//如何去校验用户名和密码的?是通过调用LoginUser中的getUsername和getPassword方法进行校验的
    }
}
  • 第四步 引入jwt工具类
public class JwtUtil {
    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "qx";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");//token用UUID来代替
        return token;
    }

    /**
        id      : 可以不用
        subject : 我们想要加密存储的数据
        ttl     : 我们想要设置的过期时间
     */

    /**
     * 生成token  jwt加密
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成token  jwt加密
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }
    /**
     * 创建token jwt加密
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }
    public static void main(String[] args) throws Exception {
        //jwt加密
        String jwt = createJWT("123456");
        System.out.println(jwt);

        //jwt解密
        Claims claims = parseJWT(jwt);
        String subject = claims.getSubject();
        System.out.println(subject);
        System.out.println(jwt);
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    /**
     * jwt解密
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}
  • 第五步 创建 LoginAndLogoutService 接口和 LoginAndLogoutServiceImpl 实现类,以及对应的Controller
@Service
public class LoginAndLogoutServiceImpl implements LoginAndLogoutService {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Result login(String username, String password) {
        //登录认证,把用户登录的用户名和密码封装到authenticationToken对象中去
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);

        //authenticationToken对象中
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //要是验证通过了,这个authenticate对象不会为null的

        //验证用户是否登录成功
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("用户名或密码错误");
        }

        //到这里就是登录成功了
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String token = JwtUtil.createJWT(userId);

        //将用户信息存入Redis
        redisTemplate.opsForValue().set(userId,loginUser);  //注意这里的LoginUser为什么可以直接存入Redis?是因为UserDetails已经序列化过了

        return new Result(200,"登录成功","Token : "+token);
    }

    @Override
    public Result logout() {

        //获取SecurityContextHolder中的用户id
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        //这里不需要考虑LoginUser存不存在的问题,能进入到这里,都是已经经过了认证的,所以肯定是存在的
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        String userId = loginUser.getUser().getId().toString();

        //删除redis中的值
        redisTemplate.delete(userId);
        return new Result(200, "成功退出登录");
    }
}
  • 第六步 引入 WebUtil 工具类 和 权限异常的全局处理登录异常的全局处理 以及 全局异常处理 的配置
public class WebUtils
{
    /**
     * 将字符串渲染到客户端
     *
     * @param response 渲染对象
     * @param string 待渲染的字符串
     * @return null
     */
    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 = new Result(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);

    }
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {

        Result result = new Result(HttpStatus.UNAUTHORIZED.value(), "认证失败请重新登录");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);
    }
}
@ControllerAdvice(annotations = {RestController.class, Controller.class})
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(value = RuntimeException.class)
    public Result handleArgsException(RuntimeException e){
        Result result = new Result();
        result.setMsg(e.getMessage());
//        result.setData(e.getStackTrace());
        result.setCode(500);
        return result;
    }
}
  • 第七步 配置一个过滤器,让用户不需要重复进行登录 JwtAuthenticationTokenFilter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if(!StringUtils.hasText(token)){
            //放行
            filterChain.doFilter(request,response);
            return;//注意这里一定要return,如果这里不加return,当后面的过滤链执行完了之后,回到这个方法后,还会往下面继续执行
        }

        //解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        //从redis中获取用户信息
        String redisKey = userId;
        LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }

        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        //过滤链放行
        filterChain.doFilter(request,response);
    }
}

  • 第八步 创建 SecurityConfig 的配置文件

    @EnableGlobalMethodSecurity(prePostEnabled = true) 这一个配置非常重要,配置了才能使用@PreAuthorize注解

    import com.zhku.fruitsandvegetables.filter.JwtAuthenticationTokenFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    
        @Bean
        PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
        @Autowired
        private AuthenticationEntryPoint authenticationEntryPoint;
    
        @Autowired
        private AccessDeniedHandler accessDeniedHandler;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            //自定义权限和认证异常处理      认证失败其实就是登录没通过
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler);
    
            //配置Token过滤器的位置,放到 UsernamePasswordAuthenticationFilter 之前
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
            //配置不要使用Session
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
            //开启SpringSecurity的允许跨域的功能
            http.cors();
    
            //关闭csrf的防护
            http.csrf().disable();
    
            http
                    //开启授权配置
                    .authorizeRequests()
                    .antMatchers("/login","/logout","/register").permitAll() //这些路径不需要登录就可以访问
                    .anyRequest().authenticated();        //其他路径都需要身份验证
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security 6中搭建授权服务器是一个相对简单的过程。下面我将简要介绍一下搭建的步骤。 首先,我们需要在Spring Boot项目中添加Spring Security和OAuth2相关的依赖。可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency> ``` 接下来,我们需要创建一个配置类来配置授权服务器。在这个类上要添加`@EnableAuthorizationServer`注解,以启用授权服务器功能。在配置类中,我们要配置一些必要的属性,比如授权服务器的端口、授权类型、客户端信息等。 ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client1") .secret("{noop}secret1") .authorizedGrantTypes("authorization_code", "refresh_token") .scopes("read", "write") .redirectUris("http://localhost:8080/login/oauth2/code/custom"); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); } } ``` 在上面的示例中,我们使用了一个内存存储方式来存储客户端信息。在实际项目中,可以根据需求选择不同的存储方式,比如使用数据库。 最后,我们需要在Spring Boot的主配置类上添加`@EnableWebSecurity`注解,以启用Web安全功能。 ```java @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/**", "/login/**", "/logout/**") .permitAll() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin") .password("{noop}password") .roles("ADMIN"); } } ``` 通过上述的步骤,我们就完成了在Spring Security 6中搭建授权服务器的过程。其中,配置授权服务器的属性和行为可以根据实际需求进行调整和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值