栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringSecurity认证

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringSecurity认证

文章目录
  • 一、SpringSecurity简介
    • 1、创建SpringSecurity项目
    • 2、测试依赖是否导入成功
  • 二、重写认证流程
    • 1、导入对应依赖
    • 2、配置数据库连接
    • 3、导入工具类
    • 4、实现UserDetails 接口
    • 5、实现UserDetailsService接口
    • 6、将BCryptPasswordEncoder注入到容器
  • 三、实现登录接口
    • 1、将AuthenticationManager注入到容器
    • 2、登录接口放行
    • 3、定义LoginService
    • 4、定义LoginController
  • 四、token认证
    • 1、定义jwt认证过滤器
    • 2、配置jwt认证过滤器
  • 五、退出登录

一、SpringSecurity简介

官方介绍:Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

1、创建SpringSecurity项目

在以前创建springboot项目基础上加上security依赖:

        
            org.springframework.boot
            spring-boot-starter-security
        
2、测试依赖是否导入成功

随便写一个controller接口,发出请求后出现以下验证页面就说明依赖第一个security项目创建成功。只有先登录验证之后才能继续访问接口,默认用户名是:user,密码在日志里面有输出,复制过来就行。

二、重写认证流程 1、导入对应依赖
        
            mysql
            mysql-connector-java
            runtime
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
            2.5.0
        
        
            com.alibaba
            fastjson
            1.2.33
        
        
            io.jsonwebtoken
            jjwt
            0.9.0
        
        
            com.alibaba
            druid-spring-boot-starter
            1.1.23
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.2
        
2、配置数据库连接
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test02
      username: root
      password: 1234

  redis:
    host: localhost
    port: 6379
    #    password: 1234
    database: 0
  cache:
    redis:
      time-to-live: 1800000

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:/mapper
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "cx";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
    
    
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        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("cx")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }

    
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    public static void main(String[] args) throws Exception {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
        Claims claims = parseJWT(token);
        System.out.println(claims);
    }

    
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    
    
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }


}
  1. 重写RedisTemplate
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    
    public  void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    
    public  void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    
    public  T getCacheObject(final String key)
    {
        ValueOperations operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    
    public  long setCacheList(final String key, final List dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    
    public  List getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    
    public  BoundSetOperations setCacheSet(final String key, final Set dataSet)
    {
        BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
        Iterator it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    
    public  Set getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    
    public  void setCacheMap(final String key, final Map dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    
    public  Map getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    
    public  void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    
    public  T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    
    public  List getMultiCacheMapValue(final String key, final Collection hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    
    public Collection keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

 
4、实现UserDetails 接口 

SpringSecurity初始验证登录信息时,是通过它自己默认的用户名以及日志中生成的密码验证的。但是我们在项目中肯定不是使用它的用户信息,我们需要验证自己数据库中存在的用户信息。UserDetails是一个返回结果接口,里面封装了认证成功之后的用户信息,我们需要将信息替换成自己数据库对用用户信息,就需要实行这个接口。将用户实体作为成员变量,再通过lombok就可以实现用户信息封装。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;
    
    
    @Override
    public Collection getAuthorities() {
        return null;
    }

    @Override
    public String 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;
    }
}
5、实现UserDetailsService接口

UserDetailsService验证登录用户是否存在,是否合法等信息的,SpringSecurity初始认证不是认证我们数据库用户信息,所有需要重写此接口。接口返回结果类型是UserDetails ,上一步我们已经重写里面的方法,返回的结果就是用户验证成功后的信息。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

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

//        查询用户信息
        LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUserName, userName);
        User user = userMapper.selectOne(wrapper);

//        未查询到用户
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名不存在");
        }

//        查询成功返回用户信息
        return new LoginUser(user);
    }
}

6、将BCryptPasswordEncoder注入到容器

BCryptPasswordEncoder类是一个加密解密类,只有将它注入到容器,替换掉SpringSecurity默认的密码验证机制PasswordEncoder ,密码验证才不需要说明验证机制。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}
三、实现登录接口 1、将AuthenticationManager注入到容器

AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager一般不直接认证,AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List列表,存放多种认证方式。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个AuthenticationProvider。

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
2、登录接口放行

在系统中并不是所有的接口都需要先认证后访问的,登录接口不需要验证就应该能够被访问,需要将不用验证的接口配置好。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }
3、定义LoginService
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Override
    public R login(User user) {
//        AuthenticationManager用户名密码认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

//        认证失败
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }

//        认证成功
//        使用用户id,生成JWT
        LoginUser principal = (LoginUser) authenticate.getPrincipal();
        Long id = principal.getUser().getId();
        String jwt = JwtUtil.createJWT(id.toString());
        Map map = new HashMap<>();
        map.put("token",jwt);

//        将用户信息存入redis
        redisCache.setCacheObject("login:"+ id, principal);

        return R.ok(map);
    }

}

4、定义LoginController
@RestController
@RequestMapping("/login")
public class LoginController {

    @Autowired
    private LoginService loginService;

    @PostMapping
    public R login(@RequestBody User user){
        return loginService.login(user);
    }
}
四、token认证 1、定义jwt认证过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//        获取token
        String token = httpServletRequest.getHeader("token");
        if (!StringUtils.hasText(token)) {
//            放行
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
//        解析token
        String id;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            id = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
//        redis获取用户信息
        String redisKey = "userid:" + id;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
//        存入securityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//        放行
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

2、配置jwt认证过滤器
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
五、退出登录
    @GetMapping("/logout")
    public R logout(){
//        从securityContextHolder获取用户id
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser principal = (LoginUser) authenticationToken.getPrincipal();
        Long id = principal.getUser().getId();
//        删除redis里面用户信息
        redisCache.deleteObject("login:"+id);

        return R.ok("注销成功");
    }
暑期编程PK赛 得CSDN机械键盘等精美礼品!
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号