java集成Spring Security(后端代码详细)

Spring Security介绍

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

正如你可能知道的关于安全方面的两个核心功能是“认证”和“授权”,一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 SpringSecurity 重要核心功能。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。

通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

通俗点讲就是系统判断用户是否有权限去做某些事情。

SpringSecurity 特点:

⚫ 和 Spring 无缝整合。

⚫ 全面的权限控制。

⚫ 专门为 Web 开发而设计。

◼旧版本不能脱离 Web 环境使用。

◼新版本对整个框架进行了分层抽取,分成核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。

⚫ 重量级。

3.2、 Shiro

Apache 旗下的轻量级权限控制框架。

特点:

⚫ 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。

⚫ 通用性。

◼好处:不局限于 Web 环境,可以脱离 Web 环境使用。

◼缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。

Spring Security实现权限

要对Web资源进行保护,最好的办法莫过于Filter 要想对方法调用进行保护,最好的办法莫过于AOP

Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。

 如图所示,一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

这里提示一下,其实Spring Security的核心逻辑全在这一套过滤器中 ,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security ,我们在学习使用的过程中还是要关注两个过滤器:UsernamePasswordAuthenticationFilter负责登录认证,              FilterSecurityInterceptor负责权限授权。

Spring Security入门

先介绍一下我的代码布局:

 common是我的工具类,spring-security子模块是我为集成spring-security新建的类

model是定义的一些实体类,

service-oa:是我处理业务逻辑的类

1:第一步:导入依赖 我导入的地方就是spring-security子模块的pom.xml文件里(这里建议使用maven的聚合工程导入到你公共使用的模块里)

        <!-- Spring Security依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<scope>provided </scope>
		</dependency>

2:第二步在spring-security子模块下创建一个config包,开始进行配置,结构如下

 下面是配置类里面的代码

@Configuration
@EnableWebSecurity   //@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableGlobalMethodSecurity(prePostEnabled = true)  //开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    /**
     *  这里的导UserDetailsService的包需要注意一下,在Spring-Security的UserDetailsService类里面需要继承一下UserDetailsService接口
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private CustomMd5PasswordEncoder customMd5PasswordEncoder;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
           http
                //关闭csrf跨站请求伪造
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                //TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
                .addFilterBefore(new TokenAuthenticationFilter(stringRedisTemplate), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new TokenLoginFilter(authenticationManager(),stringRedisTemplate));

        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(customMd5PasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
    }
}

3:第三步:spring-security子模块下创建一个custom包,跟上面的第二步的结构图一样,进行配置(加密用的是MD5加密,有想要的朋友可以私聊我要)

自定义spring-Security加密处理组件:
@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {
    /*
      密码处理
     */

    @Override
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }


}
 spring-Security 用户对象UserDetails 我们写各类继承spring-Security自己封装User类:
public class CustomUser  extends User {
    /**
     * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
     */
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

    public SysUser getSysUser() {
        return sysUser;
    }

    public void setSysUser(SysUser sysUser) {
        this.sysUser = sysUser;
    }


}
 spring-Security业务对象UserDetailsService:
public interface UserDetailsService  extends org.springframework.security.core.userdetails.UserDetailsService {
    /**
     * 根据用户名获取用户对象(获取不到直接抛异常)
     */
    @Override
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

4:接着就是spring-security子模块下创建一个filter包,跟上面的第二步的结构图一样,进行配置。

(这里重点强调一下我使用的用户登录是JWT用户登录,不熟悉的可以自行百度了解,很简单的)

认证解析token组件,判断请求头是否有token,如果有认证完成,认证解析token过滤器(注解很详细呦):
public class TokenAuthenticationFilter extends OncePerRequestFilter {

     // 通过构造方法注入
      private  StringRedisTemplate stringRedisTemplate;


    public TokenAuthenticationFilter(StringRedisTemplate stringRedisTemplate){
          this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        logger.info("uri:"+request.getRequestURI());
         // 如果是登录接口,直接放行
        if("/admin/system/index/login".equals(request.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }
        // 如果不是登录接口,则判断请求头里是否有token
        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
        if(null != authentication) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request, response);
        } else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
        }
    }

    /**
     *     如果不是登录接口,则判断请求头里是否有token
     * @param request
     * @return
     */
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
         // 请求头里面是否有token
        String token = request.getHeader("token");
        // 判断是否有token
         if (!StringUtils.isEmpty(token)){
             // 解析token获取到用户名称
             String username = JwtHelper.getUsername(token);
               // 判断用户名称不为空
                if (!StringUtils.isEmpty(username)){
                    // 通过token里面得到的用户username,去redis里面获取用户权限数据
                    String authorityString = stringRedisTemplate.opsForValue().get(username);
                    // 将redis获取字符串权限数据转换要求集合类型List<SimpleGrantedAuthority>
                    if (!StringUtils.isEmpty(authorityString)){
                        List<Map> mapsList = JSON.parseArray(authorityString, Map.class);
                        System.out.println(mapsList);
                        List<SimpleGrantedAuthority> simpleGrantedAuthorities= new ArrayList<>();
                        for (Map map : mapsList) {
                            String authoritys = (String)map.get("authority");
                            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(authoritys));
                        }
                      return new UsernamePasswordAuthenticationToken(username, null,simpleGrantedAuthorities);
                    }else {

                        return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
                    }

                }
         }
         return  null;
    }


}
登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验:
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {


     //  通过构造方法将redis引入进来
     private  StringRedisTemplate stringRedisTemplate;

    // 构造方法
        public  TokenLoginFilter(AuthenticationManager authenticationManager,StringRedisTemplate stringRedisTemplate){
            this.setAuthenticationManager(authenticationManager);
            this.setPostOnly(false);
            //指定登录接口及提交方式,可以指定任意路径
            this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));
            this.stringRedisTemplate=stringRedisTemplate;
        }


    // 登录认证
      // 获取输入的用户名和密码,调用方法
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        try {
            //  获取输入的用户信息
            LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
            //  封装对象
            Authentication authenticationToken= new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
            // 调用方法,完成认证
            return  this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            e.printStackTrace();
            throw  new  RuntimeException(e);
        }

    }


    /**
     *   认证成功调用方法
     * @param request
     * @param response
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
      @Override
      protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                              Authentication auth) throws IOException, ServletException {

          // 获取当前用户
//          Object principal = auth.getPrincipal(); 强转
          CustomUser principal =(CustomUser) auth.getPrincipal();

          // 生成token
          String token = JwtHelper.createToken(principal.getSysUser().getId(), principal.getSysUser().getUsername());

          // 获取当前用户的权限,放在redis中,用户名为key,权限数据为value
          stringRedisTemplate.opsForValue().set(principal.getUsername(), JSON.toJSONString(principal.getAuthorities()));

          // 返回----利用源生方法返回(ResponseUtil工具类)
          Map<String,Object> map = new HashMap<>();
          map.put("token",token);
          ResponseUtil.out(response, Result.ok(map));
      }


    /**
     *     认证失败调用方法
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
      @Override
      protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException e) throws IOException, ServletException {
          ResponseUtil.out(response,Result.build(null, ResultCodeEnum.LOGIN_ERROR));
      }


}

这里是这个类需要导入的一些其他依赖类:

LoginVo:

/**
 * 登录对象
 */
public class LoginVo {

    /**
     * 手机号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
Result<T>类
@Data
public class Result<T> {

    //返回码
    private Integer code;

    //返回消息
    private String message;

    //返回数据
    private T data;

    public Result(){}

    // 返回数据
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<T>();
        if (data != null){
            result.setData(data);
        }
        return result;
    }

    public static <T> Result<T> build(T body, Integer code, String message) {
        Result<T> result = build(body);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }

    public static<T> Result<T> ok(){
        return Result.ok(null);
    }

    /**
     * 操作成功
     * @param data  baseCategory1List
     * @param <T>
     * @return
     */
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }

    public static<T> Result<T> fail(){
        return Result.fail(null);
    }

    /**
     * 操作失败
     * @param data
     * @param <T>
     * @return
     */
    public static<T> Result<T> fail(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.FAIL);
    }

    public Result<T> message(String msg){
        this.setMessage(msg);
        return this;
    }

    public Result<T> code(Integer code){
        this.setCode(code);
        return this;
    }
}
ResultCodeEnum类:
@Getter
public enum ResultCodeEnum {

    SUCCESS(200,"成功"),
    FAIL(201, "失败"),
    LOGIN_ERROR(204,"认证失败"),
    SERVICE_ERROR(2012, "服务异常"),
    DATA_ERROR(204, "数据异常"),

    LOGIN_AUTH(208, "未登陆"),
    PERMISSION(209, "没有权限")
    ;

    private Integer code;

    private String message;

    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
JwtHelper类:(JWT生成登录token工具类)
public class JwtHelper {
        // token的有效时间
        private static long tokenExpiration = 365 * 24 * 60 * 60 * 1000;
        // Jwt加密的秘钥
       private static String tokenSignKey = "123456";

     /**
      根据用户id和用户名称生成token字符串
      */
     public static String createToken(Long userId, String username) {
        String token = Jwts.builder()
                // 分类
                .setSubject("AUTH-USER")
                // 设置token的有效时长
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                // 设置主题
                .claim("userId", userId)
                .claim("username", username)
                // 设置秘钥
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

     /**
       从token字符串中获取到用户的id
      */
    public static Long getUserId(String token) {
        try {
            if (StringUtils.isEmpty(token)){
                return null;
            }

            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            Integer userId = (Integer) claims.get("userId");
            return userId.longValue();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**

     从token字符串中获取到用户的id
     */
    public static String getUsername(String token) {
        try {
            if (StringUtils.isEmpty(token)){
                return "";
            }

            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return (String) claims.get("username");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "admin");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUsername(token));
    }
ResponseUtil类:
public class ResponseUtil {
    public static void out(HttpServletResponse response, Result r) {
        ObjectMapper mapper = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            mapper.writeValue(response.getWriter(), r);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
CustomUser类(spring-Security 用户对象UserDetails 我们写各类继承spring-Security自己封装User类)
public class CustomUser  extends User {
    /**
     * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
     */
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

    public SysUser getSysUser() {
        return sysUser;
    }

    public void setSysUser(SysUser sysUser) {
        this.sysUser = sysUser;
    }

5:也是最后一步,这里就需要找到你们自己项目的登录接口,集成将代码集成进去就可以了,这是我写的一个简单的登录;

@RestController
@RequestMapping("/admin/system/index")
@Api(tags = "后台登录登出")
public class IndexController {

         @Autowired
         private SysUserService sysUserService;

         @Autowired
         private SysMenuService sysMenuService;

    @PostMapping("login")
    @ApiOperation(value = "后台登录", notes = "login")
    public Result login(@RequestBody LoginVo loginVo) {
        // 1,获取到用户名和密码
        String username = loginVo.getUsername();
        if (!StringUtils.isEmpty(username)){
            // 2,根据用户的名去查询数据库
            LambdaQueryWrapper<SysUser> wrapper= new LambdaQueryWrapper<>();
            wrapper.eq(SysUser::getUsername,username);
            SysUser sysUser = sysUserService.getOne(wrapper);
            // 3, 用户信息是否存在
             if (ObjectUtils.isEmpty(sysUser)){
                 throw  new CustomException(201,"当前登录用户或密码错误");
             }
            // 4,用户密码是否正确
             if(!sysUser.getPassword().equals(MD5.encrypt(loginVo.getPassword()))){
                 throw  new CustomException(201,"当前登录用户名或密码错误");
             }
            // 5,用户是否禁用
             if (sysUser.getStatus().intValue() ==0){
                 throw  new CustomException(201,"当前登录用户被禁用");
             }
            // 6,将用户信息进行加密
            String token = JwtHelper.createToken(sysUser.getId(), sysUser.getUsername());
             Map<String,String> map = Maps.newHashMap();
              map.put("token",token);
            // 7.返回           因为cookie不能跨域,所以将token放在请求头中
            return Result.ok(map);
        }
        return Result.fail();
    }



    @GetMapping("info")
    @ApiOperation(value = "获取用户信息", notes = "info")
    public Result info(HttpServletRequest request) {
        // 1,每次都从从请求头中取出token,分析出用户的信息
        String token = request.getHeader("token");
        // 2,从token字符串获取用户id 或者用户名称
   //        Long userId =  2L;       //JwtHelper.getUserId(token);
        Long userId = JwtHelper.getUserId(token);

        // 3,根据用户id,匹配用户信息
        if (userId.longValue() ==0 || userId ==null){
             throw  new CustomException(201,"从请求中获取用户Id异常,请联系管理员");
        }
        LambdaQueryWrapper<SysUser>wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getId,userId);
        SysUser sysUser = sysUserService.getOne(wrapper);
        if (ObjectUtils.isEmpty(sysUser)){
            throw  new CustomException(201,"获取用户信息失败");
        }
        // 4,根据用户ID获取用户可以操作的菜单列表(查询数据库动态的构建路由结构,进行显示)
           List<RouterVo> routersList    =sysMenuService.findUserMenuListByUserId(userId);

        // 5,根据用户id获取用户可以操作按钮列表
        List<String>  permsList= sysMenuService.findUserPermsByUserId(userId);

        // 6,返回相应的数据

        Map<String,Object> map = Maps.newHashMap();
        map.put("roles","roles");
        map.put("name",sysUser.getName());
        map.put("avatar","");
        // 返回用户可以操作的按钮
        map.put("buttons",permsList);
        // 返回用户可以操作的菜单
        map.put("routers",routersList);
        return Result.ok(map);
    }


    @PostMapping("logout")
    @ApiOperation(value = "后台登出", notes = "logout")
    public Result logout() {
    return Result.ok();
    }




}

当然,有不足的地方希望大家私聊或评论指正,谢谢各位!!!静待您的佳音~

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值