Spring Security-6.2 and spring boot
发表于:2024-03-11 |
字数统计: 1.6k | 阅读时长: 7分钟 | 阅读量:

Spring Security-6.2 and spring boot

实现描述:在spring boot中引入新版的Security。

1、引入security

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

2、配置security

主要包含处理用户登录逻辑的AuthenticationManager以及负责放行以及其他控制的SecurityFilterChain。

引入security后,此时不做任何修改,启动项目,无论输入哪一个url都会发现进入security提供的登录页面。

此时就代表引入成功,其他的页面被security默认视为是私有资源,需要获得登录认证后授权才能访问。而登录界面由security生成,此时的账号密码可以在application.yml文件中配置。

security:
  user:
    name: test
    password: 123456

用户登录

对于security而言,登录所需的用户由UserDetails这个接口实现,同时这个接口又实现了序列化,因此对于自己项目中的实体类来说,只需要实现这个接口就可以了。

public class User implements UserDetails
/**
  * 获取用户权限
 */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {return null;}

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

@Override
public String getUsername() {return this.username;}


/**
  * 判断用户是否过期,未过期返回true
  */
@Override
public boolean isAccountNonExpired() {return true;}


/**
  * 判断账号是否被锁定
  */
@Override
public boolean isAccountNonLocked() {return true;}

/**
  * 判断用户凭据是否过期需要更新
  */
@Override
public boolean isCredentialsNonExpired() {return true;}

/**
  * 判断用户是否可用
  */
@Override
public boolean isEnabled() {return true;}

以上是需要实现的方法。

在Mapper中,引入Mybatis-plus

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.5.5</version>
</dependency>
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

此处继承BaseMapper类,不必再去创建xml。

关于登录逻辑,security交由UserDetailsService实现用户检验,因此只需要实现这个接口即可。

@Service
public class UserDetailServiceImpl implements UserDetailsService

需要实现的方法只有一个。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username));
    log.info("loadUserByUsername:" + user);
    return user;
}

此时,只是重写了security对于登录逻辑的接口,我们还需要自定义登录的内容,例如抛出用户名与密码错误的异常等。

定义登录接口

public interface UserService extends IService<User> {
    int register(User user);

    String login(User user);

}

实现登录接口

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    AuthenticationManager authenticationManager;
    
    @Resource
    UserMapper userMapper;
    
    @Resource
    PasswordEncoder passwordEncoder;
    
    @Resource
    JwtUtil jwtUtil;

    @Override
    public int register(User user) {
        String password = passwordEncoder.encode(user.getPassword());
        user.setPassword(password);
        userMapper.insert(user);
        return 0;
    }

    @Override
    public String login(User user) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        Authentication authentication = null;
        try {
            authentication = authenticationManager.authenticate(authenticationToken);
        } catch (AuthenticationException e) {
            log.info("用户名或密码错误!");
            return "用户名或密码错误!";
        }

        User user1 = (User) authentication.getPrincipal();
        Map<String, Object> map = new HashMap<>();

        map.put("username", user1.getUsername());
        log.info("username{}", user1.getUsername());
        String token = jwtUtil.createToken(map);
        System.out.println(token);
        return token;
    }
}

此处引入了认证管理器与密码编码和自定义的JWT工具类。

JWT令牌

@Component
public class JwtUtil {

    private String serectKey = "feqguifebahjfhabdsbh";

    public String createToken(Map<String, Object> map) {
        return Jwts.builder()
                .setClaims(map)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 1000))
                .signWith(SignatureAlgorithm.HS256, serectKey)
                .compact();
    }

    public Claims paresToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(serectKey)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            claims = e.getClaims();
        }
        return claims;
    }
}

定义一个生成token的方法和解析的方法。注意秘钥与有效期。

@Controller
public class AutoController {

    @Resource
    UserService userService;

    @PostMapping("/dologin")
    @ResponseBody
    public String login(User user) {
        String token = userService.login(user);
        return token;
    }
}

在控制层,抛出登录接口。

前端页面简单使用表单即可。

<form action="/dologin" method="post">
    账号<input type="text" name="username">
    密码<input type="password" name="password">
    <input type="submit" value="login">
</form>

此时我们只是定义了自己的登录逻辑,但还没有对security进行配置。

@Configuration
@EnableWebSecurity
public class SecurityConfig

我们需要使用两个注解,缺一不可。

在我们自己的业务类中,我们引入了密码编码,因此在此处需要定义。

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

使用@Bean方法注解写入即可。

认证管理器也需要定义,其中需要把自定义的UserDetailsService和密码管理器插入。

@Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder) {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailsService);
    provider.setPasswordEncoder(passwordEncoder);
    return new ProviderManager(provider);
}

此时来配置过滤器链。

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/register", "/")
                .permitAll()
                .anyRequest()
                .authenticated()
        );
        http.formLogin(
                form -> form.loginPage("/login")
                        .loginProcessingUrl("/dologin")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .failureForwardUrl("/demo")
//                        .successForwardUrl("/")
        );

        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
}

由于我们使用自定义的界面,所以需要禁用csrf,这个在原始的登录页面中被隐藏的用于防劫持的input标签。之后,我们需要开放所有用户和请求都可以访问的地址,其中包括登录注册和首页,除此之外的任何请求都需要验证。

之后我们需要处理登录表单,指定登录页面,和登录接口,以及参数和失败后跳转的页面,此处讲成功后跳转的页面注释,是因为会与自己的业务产生冲突。

最后需要进行JWT验证,因此在账号密码过滤器前添加了自定义的JWT的过滤器。

返回构建。

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Resource
    JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        if (token == null) {
            doFilter(request, response, filterChain);
            return;
        }
        Claims claims = null;
        claims = jwtUtil.paresToken(token);
        System.out.println("claims: " + claims);
        String username = claims.get("username", String.class);
        User user = new User();
        user.setUsername(username);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        System.out.println("authenticationToken: " + authenticationToken);
        doFilter(request, response, filterChain);
    }
}

Claims是Java中专用于对JWT声明的类,包含了JWT中声明的所有的键值对。

下一篇:
若依入门到入土