SpringBoot集成Spring Security实现登陆和简单权限验证

1.数据库配置好

2.导依赖

        <!-- spring security 安全认证 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--Token生成与解析-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>

3.新建UserDetailsServiceImpl用户认证逻辑类实现UserDetailsService接口:

package com.xr.blog.tools.security;

import com.xr.blog.exception.CustomException;
import com.xr.blog.pojo.SysUser;
import com.xr.blog.service.SysPermissionService;
import com.xr.blog.service.SysUserService;
import com.xr.blog.tools.text.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 UserDetailsServiceImpl implements UserDetailsService {
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private SysUserService userService;

    @Autowired
    private SysPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        //此处通过用户输入得用户名去数据库查找用户
        SysUser user = userService.findByUserName(username);
        //账号验证
        if (StringUtils.isNull(user)) {
            log.info("登录用户:{} 不存在.", username);
            throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getIsDisable())) {
            log.info("登录用户:{} 已被禁用.", username);
            throw new CustomException("对不起,您的账号:" + username + " 已禁用");
        }
        return createLoginUser(user);
    }


    //创建登录信息,其中loginuser实现了UserDetails接口
    public UserDetails createLoginUser(SysUser user) {
        return new LoginUser(user, permissionService.getMenuPermission(user.getId()));
    }
}

4.新建SecurityConfig类继承WebSecurityConfigurerAdapter:

package com.xr.blog.tools.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * spring security配置
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors()
                .and()
                // CRSF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类(未登录直接请求资源返回未认证)
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers("/**/login", "/**/captchaImage","/").anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/*.ico",
                        "/*.txt"
                ).permitAll()
                .antMatchers("/vendor/**").anonymous()
                .antMatchers("/images/**").anonymous()
                .antMatchers("/css/**").anonymous()
                .antMatchers("/js/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/druid/**").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
   //添加登出成功处理类logoutSuccessHandler,此时logoutUrl将失效    
 
httpSecurity.logout().logoutUrl("/**/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }


    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {

   //设置自定义认证类,并设置密码加密规则      
  auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

5.认证失败处理类AuthenticationEntryPointImpl实现AuthenticationEntryPoint,此类中得逻辑就是处理失败后返回什么信息给客户端,自定义即可:

package com.xr.blog.tools.security;

import com.alibaba.fastjson.JSON;
import com.xr.blog.tools.Result;
import com.xr.blog.tools.ServletUtils;
import com.xr.blog.tools.constant.Code;
import com.xr.blog.tools.text.StringUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
 * 认证失败处理类 返回未授权
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException {
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        ServletUtils.renderString(response, JSON.toJSONString(Result.of(Code.SC_UNAUTHORIZED.getState(), msg,msg)));
    }
}

6.退出处理类LogoutSuccessHandlerImpl实现LogoutSuccessHandler,此类用于用户退出操作时实现得逻辑:

package com.xr.blog.tools.security;

import com.alibaba.fastjson.JSON;
import com.xr.blog.tools.Result;
import com.xr.blog.tools.ServletUtils;
import com.xr.blog.tools.constant.Code;
import com.xr.blog.tools.text.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义退出处理类 返回成功
 */
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
    @Autowired
    private TokenService tokenService;

    /**
     * 退出处理
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            //String userName = loginUser.getUsername();
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken());
            // 记录用户退出日志
            //AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Code.LOGOUT, "退出成功"));
        }
        ServletUtils.renderString(response, JSON.toJSONString(Result.of(Code.SC_OK.getState(),"退出成功", "退出成功")));
    }
}

7.token认证过滤器JwtAuthenticationTokenFilter继承OncePerRequestFilter:

package com.xr.blog.tools.security;

import com.xr.blog.tools.text.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * token过滤器 验证token有效性
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

8.写一个controller层得登录接口:

package com.xr.blog.controller;

import com.xr.blog.service.LoginService;
import com.xr.blog.service.SysUserService;
import com.xr.blog.tools.Result;
import com.xr.blog.tools.constant.Code;
import com.xr.blog.tools.security.LoginBody;
import org.apache.commons.codec.DecoderException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.List;

@RestController
@RequestMapping("/squirrelblog")
public class LoginController {
    @Autowired
    LoginService loginService;
    @Autowired
    SysUserService sysUserService;

    @PostMapping(value = "/login",produces = "application/json;charset=UTF-8")
    public Result<String> login(@RequestBody LoginBody loginBody, HttpServletRequest request) throws NoSuchAlgorithmException, InvalidKeySpecException, DecoderException {
        return loginService.login(loginBody);
    }
}

9.LoginService 的login方法如下:

package com.xr.blog.service.impl;

import com.alibaba.fastjson.JSON;
import com.xr.blog.exception.CustomException;
import com.xr.blog.exception.UserPasswordNotMatchException;
import com.xr.blog.service.LoginService;
import com.xr.blog.tools.IdUtils;
import com.xr.blog.tools.Result;
import com.xr.blog.tools.constant.Code;
import com.xr.blog.tools.redis.RedisCache;
import com.xr.blog.tools.security.LoginBody;
import com.xr.blog.tools.security.LoginUser;
import com.xr.blog.tools.security.TokenService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.DecoderException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Map;

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private TokenService tokenService;
    @Autowired
    RedisCache redisCache;
    @Resource
    private AuthenticationManager authenticationManager;
    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    @Override
    public Result<String> login(LoginBody loginBody) throws NoSuchAlgorithmException, DecoderException, InvalidKeySpecException {
        //校验验证码
        String verifyKey = Code.CAPTCHA_CODE_KEY + loginBody.getUuid();
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null){
            return Result.of(Code.VAILDATE_ERROR.getState(),"验证码已过期","验证码已过期");
        }
        if(!captcha.equalsIgnoreCase(loginBody.getCode())){
            return Result.of(Code.VAILDATE_ERROR.getState(),"验证码错误","验证码错误");
        }

        // 用户验证
        Authentication authentication = null;
        try
        {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginBody.getUsername(), loginBody.getPassword()));
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                throw new UserPasswordNotMatchException();
            }
            else
            {
                throw new CustomException(e.getMessage());
            }
        }
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        return Result.of(Code.SC_OK.getState(),Code.SC_OK.getDescription(),tokenService.createToken(loginUser));
    }
}

10.接下来测试登录,未登录请求后台接口就好了。。

猜你喜欢

转载自blog.csdn.net/weixin_46792649/article/details/108704750