赞
踩
安全框架(springsecurity、shiro等)主要分为两个部分:
如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key ,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中, Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前请求是否有权限访问
- 一共有两级菜单,一级菜单的pid为1,二级菜单的pid为一级菜单的id
注意在权限管理模块使用的JWT工具类是:但是这个工具类比较简单,我就拿common_utils中的JWT工具类来说明!!!!!!
- public class JwtUtils {
-
- public static final long EXPIRE = 1000 * 60 * 60 * 24;//设置token的过期时间
- public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//秘钥加密
-
- //生成token字符串的方法(根据用户的id和昵称生成字符串)
- public static String getJwtToken(String id, String nickname){
-
- String JwtToken = Jwts.builder()
- .setHeaderParam("typ", "JWT")//设置令牌类型为JWT令牌
- .setHeaderParam("alg", "HS256")//设置签名算法为HS256
- .setSubject("guli-user")
- .setIssuedAt(new Date())
- .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置token的过期时间
- //claim是token的主体部分
- .claim("id", id)
- .claim("nickname", nickname)
- .signWith(SignatureAlgorithm.HS256, APP_SECRET) //设置签名算法和密钥
- .compact();
-
- return JwtToken;
- }
-
- /**
- * 判断token是否存在与有效
- * @param jwtToken
- * @return
- */
- public static boolean checkToken(String jwtToken) {
- if(StringUtils.isEmpty(jwtToken)) return false;
- try {
- Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- /**
- * 判断token是否存在与有效
- * @param request
- * @return
- */
- public static boolean checkToken(HttpServletRequest request) {
- try {
- String jwtToken = request.getHeader("token");//把token放在request的头信息中
- if(StringUtils.isEmpty(jwtToken)) return false;
- Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- /**
- * 根据token获取会员id
- * @param request
- * @return
- */
- //解析token,得到用户id
- public static String getMemberIdByJwtToken(HttpServletRequest request) {
- String jwtToken = request.getHeader("token");//从请求头中获取token字符串
- if(StringUtils.isEmpty(jwtToken)) return "";
- Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
- Claims claims = claimsJws.getBody();
- return (String)claims.get("id");
- }
- }

认证过滤器
认证的过程是,先从登录请求中拿到对应的用户名和密码,对用户名密码进行登录校验(与数据库中比对),认证成功后执行successfulAuthentication方法,通过JWT工具类根据用户名生成token放在客户端的cookie中然后在redis中加入一个key,value,分别是用户名和权限列表认证失败则在响应中返回错误信息。
授权过滤器
当一个用户已经登录后,下次再访问服务器时,请求头中包含token信息,
服务器先从header中获取token,解析得到用户名,从redis中获取权限列表,由springsecurity给当前用户赋予权限*(哪些可以访问,哪些不能访问)
- @Service("userDetailsService")//该bean对象的引用名就是userDetailsService
- public class UserDetailsServiceImpl implements UserDetailsService {
-
- @Autowired
- private UserService userService;
-
- @Autowired
- private PermissionService permissionService;
-
- /***
- * 根据账号获取用户信息
- * @param username:
- * @return: org.springframework.security.core.userdetails.UserDetails
- */
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- // 从数据库中取出用户信息
- User user = userService.selectByUsername(username);
-
- // 判断用户是否存在
- if (null == user){
- //throw new UsernameNotFoundException("用户名不存在!");
- }
-
- // 返回UserDetails实现类
- com.atguigu.serurity.entity.User curUser = new com.atguigu.serurity.entity.User();
- BeanUtils.copyProperties(user,curUser);
-
- //查询用户的权限列表
- List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId());
-
-
- SecurityUser securityUser = new SecurityUser(curUser);
- securityUser.setPermissionValueList(authorities);
- return securityUser;
- }
-
- }

- @Configuration
- public class CorsConfig {
- //解决跨域问题,有这个类后,其他模块的controller就不用加@CrossOrigin注解了
- @Bean
- public CorsWebFilter corsFilter() {
- CorsConfiguration config = new CorsConfiguration();
- config.addAllowedMethod("*");
- config.addAllowedOrigin("*");
- config.addAllowedHeader("*");
-
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
- source.registerCorsConfiguration("/**", config);
-
- return new CorsWebFilter(source);
- }
- }

具体跨域的问题,看下面三个博客
springboot解决跨域和集成springsecurity解决跨域
# 服务端口 server.port=8222 # 服务名 spring.application.name=service-gateway # nacos服务地址 spring.cloud.nacos.discovery.server-addr=localhost:8848 #下面都是gateway配置 #使用服务发现路由(开启nacos服务发现) spring.cloud.gateway.discovery.locator.enabled=true #设置路由id spring.cloud.gateway.routes[0].id=service-acl #设置路由的uri lb://nacos注册服务名称 spring.cloud.gateway.routes[0].uri=lb://service-acl #设置路由断言,代理servicerId为auth-service的/auth/路径 spring.cloud.gateway.routes[0].predicates= Path=/*/acl/** #配置service-edu服务 spring.cloud.gateway.routes[1].id=service-edu spring.cloud.gateway.routes[1].uri=lb://service-edu spring.cloud.gateway.routes[1].predicates= Path=/eduservice/** #配置service-ucenter服务 spring.cloud.gateway.routes[2].id=service-ucenter spring.cloud.gateway.routes[2].uri=lb://service-ucenter spring.cloud.gateway.routes[2].predicates= Path=/ucenterservice/** #配置service-ucenter服务 spring.cloud.gateway.routes[3].id=service-cms spring.cloud.gateway.routes[3].uri=lb://service-cms spring.cloud.gateway.routes[3].predicates= Path=/cmsservice/** spring.cloud.gateway.routes[4].id=service-msm spring.cloud.gateway.routes[4].uri=lb://service-msm spring.cloud.gateway.routes[4].predicates= Path=/edumsm/** spring.cloud.gateway.routes[5].id=service-order spring.cloud.gateway.routes[5].uri=lb://service-order spring.cloud.gateway.routes[5].predicates= Path=/orderservice/** spring.cloud.gateway.routes[6].id=service-order spring.cloud.gateway.routes[6].uri=lb://service-order spring.cloud.gateway.routes[6].predicates= Path=/orderservice/** spring.cloud.gateway.routes[7].id=service-oss spring.cloud.gateway.routes[7].uri=lb://service-oss spring.cloud.gateway.routes[7].predicates= Path=/eduoss/** spring.cloud.gateway.routes[8].id=service-statistic spring.cloud.gateway.routes[8].uri=lb://service-statistic spring.cloud.gateway.routes[8].predicates= Path=/staservice/** spring.cloud.gateway.routes[9].id=service-vod spring.cloud.gateway.routes[9].uri=lb://service-vod spring.cloud.gateway.routes[9].predicates= Path=/eduvod/** spring.cloud.gateway.routes[10].id=service-edu spring.cloud.gateway.routes[10].uri=lb://service-edu spring.cloud.gateway.routes[10].predicates= Path=/eduservice/**
从上面配置文件看出,网关服务(api_gateway)对外暴露的端口是8222,也就是说前端的请求url是网关的ip+8222
网关接收到请求后,网关根据请求url的字符信息,去注册中心发现对应的服务(服务发现)。
前提是各个服务已经在nacos注册中心注册了
具体的nacos服务发现的规则(也就是怎么根据请求url找到对应的nacos注册中心的服务名)由上面的配置文件规定
和nginx的配置文件中配置的是一样的意思
springboot本质上是一个过滤器链,也就是说提供很多过滤器,这里只详细介绍项目中用到的过滤器。
- /**
- * <p>
- * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验
- * </p>
- *
- * @author qy
- * @since 2019-11-08
- */
- public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
-
- private AuthenticationManager authenticationManager;
- private TokenManager tokenManager;
- private RedisTemplate redisTemplate;
-
- public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
- this.authenticationManager = authenticationManager;
- this.tokenManager = tokenManager;
- this.redisTemplate = redisTemplate;
- this.setPostOnly(false);
- this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
- }
-
- @Override
- public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
- throws AuthenticationException {
- try {
- User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
-
- return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- }
-
- /**
- * 登录成功
- * @param req
- * @param res
- * @param chain
- * @param auth
- * @throws IOException
- * @throws ServletException
- */
- @Override
- protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
- Authentication auth) throws IOException, ServletException {
- SecurityUser user = (SecurityUser) auth.getPrincipal();
- String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
- redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
-
- ResponseUtil.out(res, R.ok().data("token", token));
- }
-
- /**
- * 登录失败
- * @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, R.error());
- }
- }

- public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
-
- //1、先判断是不是post提交
- if (this.postOnly && !request.getMethod().equals("POST")) {
- throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
- } else {
-
-
- //2、从请求request获取用户名和密码
- String username = this.obtainUsername(request);
- String password = this.obtainPassword(request);
- if (username == null) {
- username = "";
- }
- if (password == null) {
- password = "";
- }
- username = username.trim();
-
- //3、查数据库,对用户名和密码进行校验
- UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
- this.setDetails(request, authRequest);
- return this.getAuthenticationManager().authenticate(authRequest);
- }
- }

- /**
- * <p>
- * 安全认证用户详情信息
- * </p>
- *
- * @author qy
- * @since 2019-11-08
- */
- @Data
- @Slf4j
- public class SecurityUser implements UserDetails {
-
- //当前登录用户
- private transient User currentUserInfo;
-
- //当前权限
- private List<String> permissionValueList;
-
- public SecurityUser() {
- }
-
- public SecurityUser(User user) {
- if (user != null) {
- this.currentUserInfo = user;
- }
- }
- // 表示获取登录用户所有权限
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- Collection<GrantedAuthority> authorities = new ArrayList<>();
- for(String permissionValue : permissionValueList) {
- if(StringUtils.isEmpty(permissionValue)) continue;
- SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
- authorities.add(authority);
- }
-
- return authorities;
- }
- // 表示获取密码
- @Override
- public String getPassword() {
- return currentUserInfo.getPassword();
- }
- // 表示获取用户名
- @Override
- public String getUsername() {
- return currentUserInfo.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;
- }
- }

- public interface UserDetails extends Serializable {
- // 表示获取登录用户所有权限
- Collection<? extends GrantedAuthority> getAuthorities();
- // 表示获取密码
- String getPassword();
- // 表示获取用户名
- String getUsername();
- // 表示判断账户是否过期
- boolean isAccountNonExpired();
- // 表示判断账户是否被锁定
- boolean isAccountNonLocked();
- // 表示凭证{密码}是否过期
- boolean isCredentialsNonExpired();
- // 表示当前用户是否可用
- boolean isEnabled();
- }

项目中DefaultPasswordEncoder实现了这个接口
- /**
- * <p>
- * t密码的处理方法类型
- * </p>
- *
- * @author qy
- * @since 2019-11-08
- */
- @Component
- public class DefaultPasswordEncoder implements PasswordEncoder {
-
- public DefaultPasswordEncoder() {
- this(-1);
- }
-
- /**
- * @param strength
- * the log rounds to use, between 4 and 31
- */
- public DefaultPasswordEncoder(int strength) {
-
- }
- //使用MD5加密密码
- public String encode(CharSequence rawPassword) {
- return MD5.encrypt(rawPassword.toString());
- }
-
- public boolean matches(CharSequence rawPassword, String encodedPassword) {
- return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
- }
- }

SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的 15 个过滤器进行说明:
(1) WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
(2) SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将SecurityContextHolder 中的信息清除,例如在 Session 中维护一个用户的安全信 息就是这个过滤器处理的。
(3) HeaderWriterFilter:用于将头信息加入响应中。
(4) CsrfFilter:用于处理跨站请求伪造。
(5)LogoutFilter:用于处理退出登录。
(6)UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改。
(7)DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
(8)BasicAuthenticationFilter:检测和处理 http basic 认证。
(9)RequestCacheAwareFilter:用来处理请求的缓存。
(10)SecurityContextHolderAwareRequestFilter:主要是包装请求对象 request。
(11)AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在
Authentication 对象,如果不存在为其提供一个匿名 Authentication。
(12)SessionManagementFilter:管理 session 的过滤器
(13)ExceptionTranslationFilter:处理 AccessDeniedException 和
AuthenticationException 异常。
(14)FilterSecurityInterceptor:可以看做过滤器链的出口。
(15)RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
Spring Security 采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器:
绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以使用 Spring Security 提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在 配置类的configure方法中配置,没有配置不生效。下面会重点介绍以下三个过滤器:
UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证。
ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。
FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由 ExceptionTranslationFilter 过滤器进行捕获和处理。
认证流程是在 UsernamePasswordAuthenticationFilter 过滤器中处理的,具体流程如下所示:
- 查看 UsernamePasswordAuthenticationFilter 的父类 AbstractAuthenticationProcessingFilter,有一个doFilter方法,认证过程主要都在这个方法里
- 1、先判断提交方式是不是post,是的话进入下一个过滤器,不是的话就放行
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; if (!this.requiresAuthentication(request, response)) { chain.doFilter(request, response); } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Request is to process authentication"); } Authentication authResult; try { authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } this.sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException var8) { this.logger.error("An internal error occurred while trying to authenticate the user.", var8); this.unsuccessfulAuthentication(request, response, var8); return; } catch (AuthenticationException var9) { this.unsuccessfulAuthentication(request, response, var9); return; } if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } this.successfulAuthentication(request, response, chain, authResult); } }
- 2、 doFilter方法会调用其子类的attemptAuthentication方法进行认证,其子类的attemptAuthentication方法首先获取表单提交的用户名和密码并封装到authRequest对象中,再交给getAuthenticationManager().authenticate来处理。getAuthenticationManager().authenticate这个方法里面是调用userDetailsService接口的实现类(在service_acl模块中)来查询数据库的方法来进行身份认证!!
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } }3、认证失败就去异常中处理,执行认证失败的方法:this.unsuccessfulAuthentication(request, response, var8);
4、认证成功就执行认证成功的方法:this.successfulAuthentication(request, response, chain, authResult);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。