SpringSecurity 项目演示

前言

        本文主要用来演示SpringSecurity.

准备工作

        一.我们需要创建一个基于SpringBoot的Maven项目 并且 配置依赖项

新建项目

        

 配置pom依赖 

 <parent> 是父依赖 放在pom文件靠上的地方  <dependencies> 放在<parent> 下面

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
  </parent>
<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </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>
    </dependency>

    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>jquery</artifactId>
      <version>3.3.1-2</version>
    </dependency>

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

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


    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.35</version>
    </dependency>

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.1</version>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
    </dependency>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--redis依赖-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--fastjson依赖-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.33</version>
    </dependency>
    <!--jwt依赖-->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.0</version>
    </dependency>
  </dependencies>
二.创建我们需要的目录结构

三.配置数据库连接池以及mabatis-plus参数
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/blog
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
四.配置工具类(通用 不必过多纠结)
4.1 配置FastJsonRedisSerializer

/**
 * Redis使用FastJson序列化
 *
 * @author sg
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }


    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}
 4.2 配置JwtUtil 加密用的   

       后面使用向redis 中 存储数据的时候,我们需要对key进行jwt加密.



public class JwtUtil {

  
    public static final Long JWT_TTL = 60 * 60 *1000L;
    public static final String JWT_KEY = "xms";

    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)            
                .setSubject(subject)   
                .setIssuer("sg")     
                .setIssuedAt(now)    
                .signWith(signatureAlgorithm, secretKey) 
                .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();
    }

}

4.3 配置RedisCache

这是redis 数据库的配置



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

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

    public <T> 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> T getCacheObject(final String key)
    {
        ValueOperations<String, T> 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 <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

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

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

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

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


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

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

  
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> 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 <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

 
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}
五.配置Config 以及 Common
  5.1 配置RedisConfig 通用


@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}
5.2 配置SecurityConfig

对于需要放行的页面 我们需要单独配置一下 比如 login 登录页面 register注册页面


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//    @Bean
//    public PasswordEncoder getEncoder(){
//        return new BCryptPasswordEncoder();
//    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                .antMatchers("/swagger-ui.html").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public AuthenticationManager getManager() throws Exception {
        return super.authenticationManager();
    }
}
5.3 配置CommonResult
package com.xms.boots.Common;

import lombok.Data;
import org.springframework.stereotype.Component;

@Component
@Data
public class CommonResult {
    private int code;
    private String msg;
    private Object data;
}

正式代码

       一:配置实体类  Users

        @TableId(type = IdType.AUTO) 这句话是必须得 如果不加这句话,会找不到主键

        @Data @AllArgsConstructor @NoArgsConstructor 是lombok语法 

        implements Serializable 让实体类 实现序列化 如果用户信息的实体类没有实现序列化接口,那么就会出现序列化异常,从而导致程序出错. 序列化是为了更好的存储数据.



@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users implements Serializable {
    @TableId(type = IdType.AUTO)
    private int uid;
    private String username;
    private String password;
    private String telephone;
}
二:配置实体类 MyUserDetail

在实体类中,所有的boolean 判断 都需要更改为true 否则 会阻挡所有页面的访问

getpassword getusername  让它返回我们自己数据库中的内容


@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyUserDetail implements UserDetails {
    @Autowired
    private Users users;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

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

    @Override
    public String getUsername() {
        return users.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;
    }
}
三:配置Mapper接口

   extends BaseMapper<Users> 继承BaseMapper 是为了能够在Service中 利用mabatis-plus 更加简单的书写相关Sql语句 泛型为Users 是为了正确映射实体类的属性 更加准确的对数据进行crud


@org.apache.ibatis.annotations.Mapper
public interface UserMapper extends BaseMapper<Users> {

}
   四:配置MyUserDetailServiceImpl

        UserDetailService是一个接口 当我们继承这个接口的时候 我们可以重写其中的loadUserByUsername方法 我们可以通过mybatis-plus根据username 去数据库查询是否是对应的对象 ,如果没有,则直接抛出异常,告知security.如果有,则把查询到的对象封装为userDetail对象 返回Spring;

package com.xms.boots.Service.Impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xms.boots.Mapper.UserMapper;
import com.xms.boots.pojo.MyUserDetail;
import com.xms.boots.pojo.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class MyUserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users users =   userMapper.selectOne(wrapper);
        if (users == null){
            throw new UsernameNotFoundException("用户不存在");
        }else{
            MyUserDetail userDetail = new MyUserDetail(users);
            return userDetail;
        }

    }
}
五:配置loginService 和 loginServiceImpl

public interface loginService {

    CommonResult login(Users users);
}


@Service
public class loginServiceImpl implements loginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private CommonResult commonResult;
    @Autowired
    private RedisCache redisCache;
    @Override
    public CommonResult login(Users users) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(users.getUsername(),users.getPassword());
               Authentication obj =  authenticationManager.authenticate(authenticationToken);
               if (obj==null){
                   throw  new RuntimeException("密码错误");
               }else{
                   MyUserDetail detail = (MyUserDetail) obj.getPrincipal();
                   Users user = detail.getUsers();
                   String jwt = JwtUtil.createJWT(user.getUid()+"");
                   commonResult.setCode(200);
                   commonResult.setMsg("验证成功");
                   HashMap map = new HashMap();
                   map.put("token",jwt);
                   commonResult.setData(map);
                   redisCache.setCacheObject(user.getUid()+"",detail);
               }
               return commonResult;
    }
}
   六: 配置JwtAuthenticationTokenFilter

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisCache redisCache;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain Chain) throws ServletException, IOException {
        String token = request.getHeader("token");
        if (token == null || "".equals(token)){
//            throw new RuntimeException("非法访问,请先登录");
            Chain.doFilter(request,response);
            return;
        }
        String uid;
       // 解析token
        try{
            Claims claims = JwtUtil.parseJWT(token);
            uid = claims.getSubject();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
//        String rediskey = "uid:" + uid;
        MyUserDetail detail = redisCache.getCacheObject(uid);
        if (detail == null){
            throw new RuntimeException("请重新登录");
        }else{
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(detail,null,null);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            Chain.doFilter(request,response);
        }

    }
}
七:配置 UserController        

@Controller
@Data
public class UserController {
    @Autowired
    private loginService loginService;
    @RequestMapping("/user/login")
    @ResponseBody
    public CommonResult login(Users users){
        return loginService.login(users);
    }
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值