springboot集成shiro+jwt详解+完整实例

本文详细介绍如何在项目中集成Shiro和JWT,实现用户登录校验、token携带及接口权限管理,涉及自定义Realm、ModularRealmAuthenticator和JwtFilter的配置。
Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

目录

简介

目的

需要的jar

集成过程

1.配置shiro

2.创建自定义Realm

2.1 LoginRealm用于处理用户登录

2.2  JwtRealm用于在登录之后,用户的token是否正确以及给当前用户授权等

2.3 OurModularRealmAuthenticator用于匹配的相应的Realm

2.4 DataAutoToken及实现类

2.5 JwtFilter处理在shiro配置的自定义的Filter

2.6 controller层登录和其他接口

2.7 service层

2.8 jwt工具类

2.9 其他的一些工具类

2.10 返回结果定义


简介

现在主流的安全框架分别为Shiro和Spring Security。关于两者之间的优缺点不是本文的重点,有兴趣的可以在网上搜搜,各种文章也都分析的很清楚。那么简单来说,Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。(不一定要建立所谓的五张表,我们要做到控制自如的使用

目的

通过集成shiro,jwt我们要实现:用户登录的校验;登录成功后返回成功并携带具有身份信息的token以便后续调用接口的时候做认证;对项目的接口进行权限的限定等。

需要的jar

本文使用的gradel作为jar包管理工具,maven也是使用相同的jar

//shiro的jar
implementation 'org.apache.shiro:shiro-spring:1.7.1'
//jwt的jar
implementation 'com.auth0:java-jwt:3.15.0'
implementation 'com.alibaba:fastjson:1.2.76'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

集成过程

1.配置shiro

@Configuration
public class ShiroConfig {
    /*
     * 解决spring aop和注解配置一起使用的bug。如果您在使用shiro注解配置的同时,引入了spring
     * aop的starter,会有一个奇怪的问题,导致shiro注解的请求,不能被映射 
     */  
    @Bean
    public static DefaultAdvisorAutoProxyCreator creator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    /**
     * Enable Shiro AOP annotation support. --<1>
     *
     * @param securityManager Security Manager
     * @return AuthorizationAttributeSourceAdvisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Use for login password matcher --<2>
     *
     * @return HashedCredentialsMatcher
     */
    @Bean("hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // set name of hash
        matcher.setHashAlgorithmName("SHA-256");
        // Storage format is hexadecimal
        matcher.setStoredCredentialsHexEncoded(true);
        return matcher;
    }

    /**
     * Realm for login --<3>
     *
     * @param matcher password matcher
     * @return PasswordRealm
     */
    @Bean
    public LoginRealm loginRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
        LoginRealm loginRealm = new LoginRealm(LOGIN);
        loginRealm.setCredentialsMatcher(matcher);
        return loginRealm;
    }

    /**
     * JwtReal, use for token validation --<4>
     *
     * @return JwtRealm
     */
    @Bean
    public JwtRealm jwtRealm() {
        return new JwtRealm(JWT);
    }

    //  --<5>
    @Bean
    public OurModularRealmAuthenticator userModularRealmAuthenticator() {
        // rewrite ModularRealmAuthenticator
        DataAuthModularRealmAuthenticator modularRealmAuthenticator = new DataAuthModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }


    // --<6>
    @Bean(name = "securityManager")
    public SecurityManager securityManager(
            @Qualifier("userModularRealmAuthenticator") OurModularRealmAuthenticatormodular,
            @Qualifier("jwtRealm") JwtRealm jwtRealm,
            @Qualifier("loginRealm") LoginRealm loginRealm
    ) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // set realm
        manager.setAuthenticator(modular);
        // set to use own realm
        List<Realm> realms = new ArrayList<>();
        realms.add(loginRealm);
        realms.add(jwtRealm);
        manager.setRealms(realms);
        // close Shiro's built-in session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);

        return manager;
    }

    // --<7>
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filter = new LinkedHashMap<>(1);
        filter.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filter);

        Map<String, String> filterMap = new HashMap<>();
        filterMap.put("/login/**", "anon");
        filterMap.put("/v1/**", "jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }
}
  1. 开启shiro注解支持,具体原理请参考shiro中AuthorizationAttributeSourceAdvisor作用

  2. 配置shiro登录验证的密码加密方式:Shiro 提供了用于加密密码验证密码服务的 CredentialsMatcher 接口,HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类。

  3. LoginRealm:自定义的Realm,用于处理用户登录验证的Realm,在shiro中验证及授权等信息会在Realm中配置,详细解释请参考shiro简介

  4. JwtRealm:自定义的Realm,用户在登录后访问服务时做token的校验,用户权限的校验等。

  5. 配置DataAuthModularRealmAuthenticator:是在项目中存在多个Realm时,根据项目的认证策略可以选择匹配需要的Realm。

  6. SecurityManager:Shiro的核心组件,管理着认证、授权、会话管理等,在这里我把所有的自定义的Realm等资源加入到SecurityManager中

  7. Shiro的过滤器:定制项目的path过滤规则,并将我们自定义的Filter加入到Shiro中的shiroFilterFactoryBean中

2.创建自定义Realm

2.1 LoginRealm用于处理用户登录

public class LoginRealm extends AuthorizingRealm {
    public LoginRealm(String name) {
        setName(name);
    }

    // 获取user相关信息的service类
    @Autowired
    private UserLoginService userLoginService;

    // supports方法必须重写,这是shiro处理流程中的一部分,他会通过此方法判断realm是否匹配的正确
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof LoginDataAutoToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        LoginDataAutoToken token = (LoginDataAutoToken) auth;
        serviceLog.info(token.getUsername() + "password auth start...");
        User user = userLoginService.selectUserByName(token.getUsername());
        if (user == null) throw new UnknownAccountException();
        Object credentials = user.getPassword();
        // save username and role to Attribute
        ServletUtils.userNameRoleTo.accept(user.getUserName(), (int) user.getUserType());
        return new SimpleAuthenticationInfo(user, credentials, super.getName());
    }
}

2.2  JwtRealm用于在登录之后,用户的token是否正确以及给当前用户授权等

public class JwtRealm extends AuthorizingRealm {
    public JwtRealm(String name) {
        setName(name);
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtDataAutoToken;
    }

    // 给当前用户授权,只有在访问的接口上配置了shiro的权限相关的注解的时候才会进入此方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserEnum.Type userEnum = EnumValue.dataValueOf(
                UserEnum.Type.class,
                ServletUtils.userNameRoleFrom.get().getUserRole()
        );
        Set<String> roles = new HashSet<>();
        roles.add(userEnum.getDesc());
        // 授权角色如果有其他的权限则都已此类的方式授权
        authorizationInfo.setRoles(roles);
        return authorizationInfo;
    }

    // 验证此次request携带的token是否正确,如果正确解析当前token,并存入上下文中
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        // verify token
        String token = (String) auth.getCredentials();
        TokenUtils.verify(token);
        TupleNameRole tupleNameRole = TokenUtils.tokenDecode(token);
        ServletUtils.userNameRoleTo.accept(tupleNameRole.getUsername(), tupleNameRole.getUserRole());
        return new SimpleAuthenticationInfo(token, token, ((JwtDataAutoToken) auth).getName());
    }
}

2.3 OurModularRealmAuthenticator用于匹配的相应的Realm

public class DataAuthModularRealmAuthenticator extends ModularRealmAuthenticator {
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        DataAutoToken dataAutoToken = (DataAutoToken) authenticationToken;

        Realm realm = getRealm(dataAutoToken);
        return doSingleRealmAuthentication(realm, authenticationToken);
    }

    private Realm getRealm(DataAutoToken dataAutoToken) {
        for (Realm realm : getRealms()) {
            // 根据定义的realm的name和dataAutoToken的name匹配相应的realm 
            if (realm.getName().contains(dataAutoToken.getName())) {
                return realm;
            }
        }
        return null;
    }
}

2.4 DataAutoToken及实现类

DataAuthModularRealmAuthenticator的doSingleRealmAuthentication(realm, authenticationToken)做检验的时候需要两个参数,一个是Realm另一个是我们定义的储存验证信息的AuthenticationToken或者它的实现类。

DataAutoToken:

public interface DataAutoToken {
    String getName();
}

 LoginDataAutoToken :

public class LoginDataAutoToken extends UsernamePasswordToken implements DataAuthToken {
    public LoginDataAuthToken(final String username, final String password) {
        super(username, password);
    }

    @Override
    public String getName() {
        return LOGIN;
    }
}

 JwtDataAutoToken:

public class JwtDataAutoToken implements AuthenticationToken, DataAuthToken {
    private final String token;

    public JwtDataAuthToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    @Override
    public String getName() {
        return JWT;
    }
}

2.5 JwtFilter处理在shiro配置的自定义的Filter

此类用于处理不在登录下必须携带发行的Token访问接口,如果Token存在,则使用shiro subject做token的和访问权限的校验。

public class JwtFilter extends BasicHttpAuthenticationFilter {
    private final BiConsumer<ServletResponse, ErrorMessage> writeResponse = (response, message) ->
            Utils.renderString.accept(
                    (HttpServletResponse) response,
                    JSON.toJSONString(ResponseResult.fail(message), SerializerFeature.WriteMapNullValue)
            );

    /**
     * @param request     ServletRequest
     * @param response    ServletResponse
     * @param mappedValue mappedValue
     * @return 是否成功
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        //input request to request log file
        requestLog.info(
                "path:{}, method:{}",
                httpServletRequest.getServletPath(),
                httpServletRequest.getMethod()
        );
        String token = httpServletRequest.getHeader(Constant.TOKEN);
        if (token != null) {
            return executeLogin(request, response);
        } else {
            writeResponse.accept(response, ErrorMessage.TOKEN_NOT_EXIST);
            return false;
        }
    }

    /**
     * execute login
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(Constant.TOKEN);
        try {
            JwtDataAuthToken jwtToken = new JwtDataAuthToken(token);
            // validate user permission
            getSubject(request, response).login(jwtToken);
            return true;
        } catch (AuthenticationException e) {
            Throwable throwable = e.getCause();
            if (throwable instanceof TokenExpiredException) {
                writeResponse.accept(response, ErrorMessage.TOKEN_HAS_EXPIRED);
            } else {
                writeResponse.accept(response, ErrorMessage.TOKEN_INVALID);
            }
        }
        return false;
    }

    /**
     * support across domains
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

2.6 controller层登录和其他接口

@RestController
public class AuthController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public ResponseResult<String> login(@RequestBody UserReqDto userReqDto) {
        userService.login(userLoginReqDto.getUsername(), userReqDto.getPassword());
        return ResponseResult.success();
    }

    // shiro角色注解,admin才可以访问此接口
    @RequiresRoles("admin")
    @PostMapping("/v1/user")
    public ResponseResult<String> addUser(@RequestBody UserAddReqDto userAddReqDto) {
        userService.add(userAddReqDto);
        return ResponseResult.success();
    }


    @PostMapping("/v1/token/verify")
    public ResponseResult<String> verify() {
        return ResponseResult.success(false);
    }

 
    @PostMapping("/v1/token/refresh")
    public ResponseResult<String> refresh() {
        return ResponseResult.success();
    }
}

2.7 service层

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void login(String username, String password) {
        // Use shiro to verify the username and password
        Subject subject = SecurityUtils.getSubject();
        LoginDataAutoToken token = new LoginDataAutoToken(username, password);
        subject.login(token);
    }

    @Transactional
    @Override
    public void add(UserAddReqDto dto) {
        User user = getUserByName.apply(dto.getUsername());
        if (user != null) {
            throw new DataAuthException(ErrorMessage.USER_ALREADY_EXISTS);
        } else {
            User newUser = new User();
            // 设置user的信息
            post(newUser); // insert user to database
        }
}

2.8 jwt工具类

public final class TokenUtils {
    private TokenUtils() {
    }

    /**
     * @param username username
     * @param role     user role
     * @return The encrypted token
     */
    public static String createToken(String username, int role) {
        Date date = new Date(System.currentTimeMillis() + Constant.TOKEN_EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(username);
        return JWT.create()
                .withClaim(Constant.USER_NAME, username)
                .withClaim(Constant.USER_ROLE, role)
                .withExpiresAt(date)
                .sign(algorithm);
    }

    /**
     * @param username username
     * @param role     user role
     * @return The encrypted token
     */
    public static String refreshToken(String username, int role) {
        return createToken(username, role);
    }

    /**
     * refresh token and add to header
     */
    public static void refreshToken() {
        TupleNameRole tupleNameRole = ServletUtils.userNameRoleFrom.get();
        ServletUtils.addHeader.accept(
                Constant.TOKEN,
                createToken(tupleNameRole.getUsername(), tupleNameRole.getUserRole())
        );
    }

    /**
     * verify token
     *
     * @param token jwtToken
     */
    public static void verify(String token) {
        try {
            TupleNameRole tupleNameRole = tokenDecode(token);
            Algorithm algorithm = Algorithm.HMAC256(tupleNameRole.getUsername());
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim(Constant.USER_NAME, tupleNameRole.getUsername())
                    .withClaim(Constant.USER_ROLE, tupleNameRole.getUserRole())
                    .build();
            verifier.verify(token);
        } catch (JWTVerificationException e) {
            serviceLog.error("token verify fail.", e);
            throw e;
        }
    }

    /**
     * @param token token
     * @return user name and role
     */
    public static TupleNameRole tokenDecode(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return new TupleNameRole(
                    jwt.getClaim(Constant.USER_NAME).asString(),
                    jwt.getClaim(Constant.USER_ROLE).asInt()
            );
        } catch (JWTDecodeException e) {
            serviceLog.error("Token decode happen exception.", e);
            throw e;
        }
    }
}

2.9 其他的一些工具类

ServletUtils:与spring context中有关的一些方法
public final class ServletUtils {
    private ServletUtils() {
    }

    private static final int SCOPE = RequestAttributes.SCOPE_REQUEST;

    private static final Supplier<ServletRequestAttributes> servletRequestAttributes = () ->
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

    private static final Supplier<HttpServletRequest> request = () -> servletRequestAttributes.get().getRequest();

    private static final Supplier<HttpServletResponse> response = () -> servletRequestAttributes.get().getResponse();

    private static final Consumer<String> saveUsernameToAttribute = (name) ->
            servletRequestAttributes.get().setAttribute(Constant.USER_NAME, name, SCOPE);

    private static final Supplier<String> usernameFromAttribute = () ->
            (String) servletRequestAttributes.get().getAttribute(Constant.USER_NAME, SCOPE);

    private static final Consumer<Integer> saveUserRoleToAttribute = (role) ->
            servletRequestAttributes.get().setAttribute(Constant.USER_ROLE, role, SCOPE);

    private static final Supplier<Integer> userRoleFromAttribute = () ->
            (Integer) servletRequestAttributes.get().getAttribute(Constant.USER_ROLE, SCOPE);

    /**
     * get token form current request
     */
    public static Supplier<String> tokenFromRequest = () -> request.get().getHeader(Constant.TOKEN);

    /**
     * save current user name and role to attribute
     */
    public static BiConsumer<String, Integer> userNameRoleTo = (name, role) -> {
        saveUsernameToAttribute.accept(name);
        saveUserRoleToAttribute.accept(role);
    };

    /**
     * get user name and role from attribute
     */
    public static Supplier<TupleNameRole> userNameRoleFrom = () ->
            new TupleNameRole(usernameFromAttribute.get(), userRoleFromAttribute.get());

    /**
     * add message to response header
     */
    public static BiConsumer<String, String> addHeader = (key, value) -> response.get().addHeader(key, value);
}

Utils:提供与shiro相同的密码加密方式、获取uuid、shiro的Filter层出错不能使用全局异常处理时的返回信息定制等。

public final class Utils {
    private Utils() {
    }

    /**
     * use sha256 encrypt
     */
    public static Function<String, String> encryptPassword = (password) -> new Sha256Hash(password).toString();

    /**
     * get uuid
     */
    public static Supplier<String> uuid = () -> UUID.randomUUID().toString().replace("-", "");

    /**
     * writer message to response
     */
    public static BiConsumer<HttpServletResponse, String> renderString = (response, body) -> {
        response.setStatus(HttpStatus.OK.value());
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=UTF-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.print(body);
        } catch (IOException e) {
            serviceLog.error("response error.", e);
        }
    };
}

2.10 返回结果定义

@Data
public class ResponseResult<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    private final String code;

    @JSONField(ordinal = 1)
    private final String msg;

    @JSONField(ordinal = 2)
    private T data;

    private ResponseResult(String code, String msg) {
        this.code = code;
        this.msg = msg;
        log();
    }

    private static <T> ResponseResult<T> create(String code, String msg) {
        return new ResponseResult<>(code, msg);
    }

    /**
     * No data returned successfully
     *
     * @return ResponseResult<String>
     */
    public static <T> ResponseResult<T> success() {
        return success(true);
    }

    /**
     * No data returned successfully
     *
     * @param refreshToken Whether to refresh token
     * @return ResponseResult<String>
     */
    public static <T> ResponseResult<T> success(boolean refreshToken) {
        if (refreshToken) TokenUtils.refreshToken();
        return create(ErrorMessage.SUCCESS.code(), ErrorMessage.SUCCESS.msg());
    }

    public static <T> ResponseResult<T> success(T data) {
        return success(data, true);
    }

    /**
     * Data returned successfully
     *
     * @param data         data
     * @param <T>          T
     * @param refreshToken Whether to refresh token
     * @return ResponseResult<T>
     */
    public static <T> ResponseResult<T> success(T data, boolean refreshToken) {
        ResponseResult<T> responseResult = success(refreshToken);
        responseResult.setData(data);
        return responseResult;
    }

    /**
     * @param e DCException
     * @return ResponseResult<String>
     */
    public static ResponseResult<String> fail(DataAuthException e) {
        return create(e.getCode(), e.getMsg());
    }

    /**
     * @param errorMessage ErrorMessage
     * @return ResponseResult<String>
     */
    public static ResponseResult<String> fail(ErrorMessage errorMessage) {
        return create(errorMessage.code(), errorMessage.msg());
    }

    /**
     * @param errorMessage DCException
     * @return ResponseResult<String>
     */
    public static ResponseResult<String> fail(ErrorMessage errorMessage, Object[] detailMessage) {
        return create(errorMessage.code(), errorMessage.msg() + Arrays.toString(detailMessage));
    }

    // Output the information returned
    private void log() {
        requestLog.info("code:{}, msg:{}", this.getCode(), this.getMsg());
    }
}

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Timmer丿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值