-
Notifications
You must be signed in to change notification settings - Fork 16
四、快速开始(Quick Start)
ZeroOrInfinity edited this page Nov 11, 2020
·
20 revisions
<dependency>
<groupId>top.dcenter</groupId>
<artifactId>ums-core-spring-boot-starter</artifactId>
<version>${top.dcenter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 第三方授权登录默认会按照下面的优先级自行寻找一种 HTTP 工具依赖,java 11 HttpClient -> OkHttp3 -> apache HttpClient -> hutool-http
示例使用 apache HttpClient .
注意: 如果是 JDK11 则不需要此依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
server:
port: 9090
spring:
profiles:
active: dev
# mysql
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/ums?useSSL=false&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: root
password: 123456
# session 简单配置
session:
# session 存储模式设置, 要导入相应的 spring-session 类的依赖, 默认为 none, 分布式应用把 session 放入 redis 等中间件
store-type: none
# session 过期时间
timeout: PT300s
# thymeleaf
thymeleaf:
encoding: utf-8
prefix: classpath:/templates/
suffix: .htm
servlet:
content-type: text/html;charset=UTF-8
# ums core
ums:
# ================ 第三方授权登录相关配置 ================
oauth:
# 是否支持第三方授权登录功能, 默认: 空, 必须明确配置是否支持
enabled: true
# 第三方服务商: providerId
github:
# 根据是否有设置 clientId 来动态加载相应 JustAuth 的 AuthXxxRequest
client-id: 4d4ee00e82f669f2ea8d
client-secret: 953ddbe871a08d6924053531e89ecc01d87195a8
gitee:
client-id: dcc38c801ee88f43cfc1d5c52ec579751c12610c37b87428331bd6694056648e
client-secret: f7384969d0e93088cdab546a1cea026442c16e2476cedec02e202cc89ee773ae
# 第三方登录授权登录 url 前缀, 不包含 ServletContextPath,默认为 /auth2/authorization.
auth-login-url-prefix: /auth2/authorization
# 第三方登录回调处理 url 前缀 ,也就是 RedirectUrl 的前缀, 不包含 ServletContextPath,默认为 /auth2/login.
redirect-url-prefix: /auth2/login
# 第三方登录回调的域名, 例如:http://localhost 默认为 "http://127.0.0.1",
# redirectUrl 直接由 {domain}/{servletContextPath}/{redirectUrlPrefix}/{providerId}(ums.oauth.[qq/gitee/weibo])组成
domain: http://localhost:9090
proxy:
# 用于国内代理(HttpClient)超时, 默认 PT3S
timeout: PT3S
# 用于国外网站代理(HttpClient)超时, 默认 PT15S
foreign-timeout: PT150S
client:
# ========= 放行权限 ==========
# 权限表达式, 当 enableRestfulApi=true 且没有 @EnableGlobalMethodSecurity 注释时生效, 默认为 hasPermission(request, authentication).
# hasPermission 表达式默认实现为 UriAuthoritiesPermissionEvaluator, 想自定义逻辑, 实现 PermissionEvaluator 即可替换.
# String accessExp = "hasPermission(request, authentication)";
# // 配置等效与
# httpSecurity.authorizeRequests().anyRequest().access(hasPermission(request, authentication));
restful-access-exp: permitAll()
# 是否支持 restful Api (前后端交互接口的风格; 如: 查询(GET),添加(POST),修改(PUT),删除(DELETE)), 默认: true.
# 当 {@code enableRestfulApi=false} 时 {@code accessExp} 权限表达式生效,
# 当 {@code enableRestfulApi=true} 时 {@code restfulAccessExp} 权限表达式生效.
enable-restful-api: true
# ================ 密码登录, session, remember-me, csrf等配置 ================
# 设置登录后返回格式(REDIRECT 与 JSON): 默认 JSON
login-process-type: redirect
# 登录页(必须自己实现)
login-page: /login
# 登录失败页(必须自己实现)
failure-url: /login
# 登录成功页(必须自己实现)
success-url: /
# 设置登出 url, 默认为 /logout
logout-url: /logout
# 设置登出后跳转的 url(必须自己实现), 默认为 /login
logout-success-url: /login
# 不需要认证的静态资源 urls, 例如: /resources/**, /static/**
ignoring-urls:
- /static/**
# 不需要认证的 uri(可以带 HttpMethod 后缀; 用:隔开), 例如: /user/** 或 /user/**:post, 默认为 空 Set.
permit-urls:
- /hello:GET
- /login
# 设置登录时用户名的 request 参数名称, 默认为 username
usernameParameter: username
# 设置登录时用户密码的 request 参数名称, 默认为 password
passwordParameter: password
# ================ 验证码配置 ================
# 同一个 uri 由多种验证码同时配置, **优先级**如下:
# `SMS > CUSTOMIZE > SELECTION > TRACK > SLIDER > IMAGE`
codes:
# 图片验证码
image:
# 设置需要图片验证码认证的 uri(必须是非 GET 请求),多个 uri 用 “-” 或 ","号分开支持通配符,如:/hello,/user/*;默认为 /authentication/form
auth-urls:
- /authentication/form
request-param-image-code-name: imageCode
# 短信验证码
sms:
# 设置需要短信验证码认证的 uri(必须是非 GET 请求),多个 uri 用 “,”号分开支持通配符,如:/hello,/user/*;默认为 /authentication/form
auth-urls:
- /authentication/mobile
request-param-mobile-name: mobile
request-param-sms-code-name: smsCode
# ================ 手机登录配置 ================
mobile:
login:
# 手机验证码登录是否开启, 默认 false,
# 手机验证码登录开启后 必须配置 ums.codes.sms.auth-urls=/authentication/mobile
sms-code-login-is-open: true
# 手机验证码登录请求处理url, 默认 /authentication/mobile
login-processing-url-mobile: /authentication/mobile
# ================ 签到配置 ================
sign:
# redis key(String) 转 byte[] 转换时所用的 charset, 默认: StandardCharsets.UTF_8
charset: UTF_8
# 用于 redis 签到 key 前缀,默认为: u:sign:
sign-key-prefix: 'u:sign:'
# 用于 redis 总签到 key 前缀,默认为: total:sign:
total-sign-key-prefix: 'total:sign:'
# 获取最近几天的签到情况, 不能大于 28 天, 默认为 7 天
last-few-days: 7
# 用户签到 redis key TTL, 默认: 二个月 , 单位: 秒
total-expired: 5356800
# 用户签到统计 redis key TTL, 默认: 二个月 , 单位: 秒
user-expired: 2678400
---
spring:
profiles: dev
mvc:
throw-exception-if-no-handler-found: true
thymeleaf:
cache: false
#debug: true
server:
port: 9090
servlet:
context-path: /demo
logging:
config: classpath:logback-spring.xml
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:- } --- [%thread] %X{MDC_TRACE_ID} %logger[%L] - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 只有这个日志权限才能看,sql语句 -->
<level>DEBUG</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
<logger name="top.dcenter" level="INFO" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="demo" level="INFO" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</configuration>
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import me.zhyd.oauth.model.AuthUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.lang.NonNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
import top.dcenter.ums.security.common.enums.ErrorCodeEnum;
import top.dcenter.ums.security.core.api.service.UmsUserDetailsService;
import top.dcenter.ums.security.core.exception.RegisterUserFailureException;
import top.dcenter.ums.security.core.exception.UserNotExistException;
import java.util.ArrayList;
import java.util.List;
/**
* 用户密码与手机短信登录与注册服务:<br><br>
* 1. 用于第三方登录与手机短信登录逻辑。<br><br>
* 2. 用于用户密码登录逻辑。<br><br>
* 3. 用户注册逻辑。<br><br>
* @author YongWu zheng
* @version V1.0 Created by 2020/9/20 11:06
*/
@Service
public class UserDetailsServiceImpl implements UmsUserDetailsService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 用户名
*/
public static final String PARAM_USERNAME = "username";
/**
* 密码
*/
public static final String PARAM_PASSWORD = "password";
private final ObjectMapper objectMapper;
private final JdbcTemplate jdbcTemplate;
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired(required = false)
private UserCache userCache;
/**
* 用于密码加解密
*/
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired
private PasswordEncoder passwordEncoder;
public UserDetailsServiceImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@SuppressWarnings("AlibabaUndefineMagicConstant")
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try
{
// 从缓存中查询用户信息:
// 从缓存中查询用户信息
if (this.userCache != null)
{
UserDetails userDetails = this.userCache.getUserFromCache(username);
if (userDetails != null)
{
return userDetails;
}
}
// 根据用户名获取用户信息
// 获取用户信息逻辑。。。
// ...
// 示例:只是从用户登录日志表中提取的信息,
log.info("Demo ======>: 登录用户名:{}, 登录成功", username);
return new User(username,
passwordEncoder.encode("admin"),
true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_VISIT, ROLE_USER"));
}
catch (Exception e)
{
String msg = String.format("Demo ======>: 登录用户名:%s, 登录失败: %s", username, e.getMessage());
log.error(msg, e);
throw new UserNotExistException(ErrorCodeEnum.QUERY_USER_INFO_ERROR, e, username);
}
}
@Override
public UserDetails registerUser(String mobile) throws RegisterUserFailureException {
if (mobile == null)
{
throw new RegisterUserFailureException(ErrorCodeEnum.MOBILE_NOT_EMPTY, null);
}
// 用户信息持久化逻辑。。。
// ...
log.info("Demo ======>: 手机短信登录用户 {}:注册成功", mobile);
User user = new User(mobile,
passwordEncoder.encode("admin"),
true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_VISIT, ROLE_USER")
);
// 把用户信息存入缓存
if (userCache != null)
{
userCache.putUserInCache(user);
}
return user;
}
@Override
public UserDetails registerUser(ServletWebRequest request) throws RegisterUserFailureException {
String username = getValueOfRequest(request, PARAM_USERNAME, ErrorCodeEnum.USERNAME_NOT_EMPTY);
String password = getValueOfRequest(request, PARAM_PASSWORD, ErrorCodeEnum.PASSWORD_NOT_EMPTY);
// ...
// UserInfo userInfo = getUserInfo(request)
// 用户信息持久化逻辑。。。
// ...
String encodedPassword = passwordEncoder.encode(password);
log.info("Demo ======>: 用户名:{}, 注册成功", username);
User user = new User(username,
encodedPassword,
true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_VISIT, ROLE_USER")
);
// 把用户信息存入缓存
if (userCache != null)
{
userCache.putUserInCache(user);
}
return user;
}
@Override
public UserDetails registerUser(@NonNull AuthUser authUser, @NonNull String username, @NonNull String defaultAuthority,
String decodeState) throws RegisterUserFailureException {
// 第三方授权登录不需要密码, 这里随便设置的, 生成环境按自己的逻辑
String encodedPassword = passwordEncoder.encode(authUser.getUuid());
// 这里的 decodeState 可以根据自己实现的 top.dcenter.ums.security.core.oauth.service.Auth2StateCoder 接口的逻辑来传递必要的参数.
// 比如: 第三方登录成功后的跳转地址
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 假设 decodeState 就是 redirectUrl, 我们直接把 redirectUrl 设置到 request 上
// 后续经过成功处理器时直接从 requestAttributes.getAttribute("redirectUrl", RequestAttributes.SCOPE_REQUEST) 获取并跳转
if (requestAttributes != null) {
requestAttributes.setAttribute("redirectUrl", decodeState, RequestAttributes.SCOPE_REQUEST);
}
// 当然 decodeState 也可以传递从前端传到后端的用户信息, 注册到本地用户
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(defaultAuthority);
// ... 用户注册逻辑
log.info("Demo ======>: 用户名:{}, 注册成功", username);
// @formatter:off
UserDetails user = User.builder()
.username(username)
.password(encodedPassword)
.disabled(false)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.authorities(grantedAuthorities)
.build();
// @formatter:off
// 把用户信息存入缓存
if (userCache != null)
{
userCache.putUserInCache(user);
}
return user;
}
@Override
public UserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
UserDetails userDetails = loadUserByUsername(userId);
User.withUserDetails(userDetails);
return User.withUserDetails(userDetails).build();
}
@Override
public List<Boolean> existedByUsernames(String... usernames) throws UsernameNotFoundException {
// ... 在本地账户上查询 userIds 是否已被使用
List<Boolean> list = new ArrayList<>();
list.add(true);
list.add(false);
list.add(false);
return list;
}
private String getValueOfRequest(ServletWebRequest request, String paramName, ErrorCodeEnum usernameNotEmpty) throws RegisterUserFailureException {
String result = request.getParameter(paramName);
if (result == null)
{
throw new RegisterUserFailureException(usernameNotEmpty, request.getSessionId());
}
return result;
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import top.dcenter.ums.security.common.enums.ErrorCodeEnum;
import top.dcenter.ums.security.core.api.permission.service.AbstractUriAuthorizeService;
import top.dcenter.ums.security.core.vo.ResponseResult;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static top.dcenter.ums.security.core.util.AuthenticationUtil.responseWithJson;
/**
* @author YongWu zheng
* @version V2.0 Created by 2020/11/11 17:09
*/
@Slf4j
public class DemoUriAuthorizeServiceImpl extends AbstractUriAuthorizeService {
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
@Autowired
private ObjectMapper objectMapper;
@Override
public void handlerError(int status, HttpServletResponse response) {
try
{
responseWithJson(response, status, objectMapper.writeValueAsString(ResponseResult.fail(ErrorCodeEnum.PERMISSION_DENY)));
}
catch (IOException e)
{
log.error(String.format("权限控制错误响应异常: status=%s, error=%s", status, e.getMessage()), e);
}
}
@Override
public Map<String, Map<String, Set<String>>> getRolesAuthorities() {
// do nothing 具体看 permission-example 的 demo.permission.service.impl.UriAuthorizeServiceImpl
return new HashMap<>(0);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author YongWu zheng
* @version V1.0 Created by 2020/9/20 20:04
*/
@Controller
@Slf4j
public class UserController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/")
public String index(@AuthenticationPrincipal UserDetails userDetails, Model model) {
if (userDetails != null)
{
model.addAttribute("username", userDetails.getUsername());
model.addAttribute("roles", userDetails.getAuthorities());
}
else
{
model.addAttribute("username", "anonymous");
model.addAttribute("roles", "ROLE_VISIT");
}
return "index";
}
@GetMapping("/me")
@ResponseBody
public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails, Authentication authentication) {
Map<String, Object> map = new HashMap<>(16);
map.put("authenticationHolder", SecurityContextHolder.getContext().getAuthentication());
map.put("userDetails", userDetails);
map.put("authentication", authentication);
return map;
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>登录</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@1.11.1/dist/jquery.min.js"></script>
</head>
<body>
<h2>登录页面</h2>
<h3>表单登录</h3>
<h5>如果短信验证码与图片验证码同时配置时,优先使用短信验证码,图片验证码失效</h5>
<!-- 通过 th:action 的方式支持 csrf 或者 添加隐藏域<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> -->
<form id="reg-form" th:action="@{/authentication/form}" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" value="admin" ><p style="color: #ff0000"
id="error-name"></p></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password" value="admin"></td>
</tr>
<tr>
<td>图形验证码:</td>
<td>
<input type="text" name="imageCode">
<img class="img" th:src="@{/code/image}" style="width: 67px; height: 23px">
</td>
</tr>
<tr>
<td ><input type="checkbox" name="rememberMe" checked="true">记住我</input></td>
<td><p style="color: #ff0000" id="error-code"></p></td>
</tr>
<tr>
<td ><button id="btn-reg" type="button">登录ajax</button></td>
<!-- 通过 form submit 不能接收错误信息, 通过 ajax 可接收错误信息 -->
<td ><button type="submit">登录</button></td>
</tr>
</table>
</form>
<h3>手机登录</h3>
<form id="mobile-form" th:action="@{/authentication/mobile}" method="post">
<table>
<tr>
<td>手机号码:</td>
<td>
<input type="tel" name="mobile" value="13345678980"><p style="color: #ff0000"
id="error-name-mobile"></p>
<a th:href="@{/code/sms?mobile=13345678980}" >发送验证码</a>
</td>
</tr>
<tr>
<td>手机验证码:</td>
<td>
<input type="text" name="smsCode">
</td>
</tr>
<tr>
<td ><input type="checkbox" name="rememberMe" checked="true">记住我</input></td>
<td><p style="color: #ff0000" id="error-code-mobile"></p></td>
</tr>
<tr>
<td ><button id="btn-mobile" type="button">登录ajax</button></td>
<!-- 通过 form submit 不能接收错误信息, 通过 ajax 可接收错误信息 -->
<td ><button type="submit">登录</button></td>
</tr>
</table>
</form>
<br><br>
<h3>社交登录</h3>
<a th:href="@{/auth2/authorization/gitee}">gitee登录</a>
<a th:href="@{/auth2/authorization/github}">github登录</a>
<a th:href="@{/auth2/authorization/gitee}">github登录</a>
<dev id="basePath" th:basePath="@{/}" style="display: none"/>
</body>
<script>
var basePath = $("#basePath").attr("basePath");
$.fn.serializeObject = function()
{
let o = {};
let a = this.serializeArray();
$.each(a, function() {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
}
$(".img").click(function(){
let uri = this.getAttribute("src");
console.log(uri)
let end = uri.indexOf('?', 0);
console.log(end)
if (end === -1) {
uri = uri + '?'+ Math.random();
} else {
uri = uri.substring(0, end) + '?'+ Math.random();
}
console.log(uri)
this.setAttribute('src', uri);
});
function submitFormByAjax(url, formId, errorNameId, errorCodeId, imgId, refresh) {
return function () {
console.log(JSON.stringify($(formId).serializeObject()))
$.ajax({
// 如果用的是模板,则 url 可以使用注入的方式,会跟着配置动态改变
url: url,
data: JSON.stringify($(formId).serializeObject()),
type: "POST",
dataType: "json",
success: function (data) {
$(errorNameId).text("")
$(errorCodeId).text("")
console.log("==========注册成功============")
// 注册成功
// ...
console.log(data)
let uri = data.data.targetUrl
if (uri === null) {
uri = basePath
}
window.location.href = uri;
},
error: function (data) {
// 注册失败
$(errorNameId).text("")
$(errorCodeId).text("")
console.log("********注册失败*********")
console.log(data)
data = data.responseJSON
if (undefined !== data) {
console.log(data);
// 错误代码看ErrorCodeEnum
if (data.code >= 900 && data.code < 1000) {
$(errorNameId).text(data.msg)
} else if (data.code >= 600 && data.code < 700) {
$(errorCodeId).text(data.msg)
}
}
if (refresh) {
$(imgId).trigger("click");
}
}
})
return
};
}
$("#btn-mobile").click(
submitFormByAjax($("#mobile-form").attr("action"), "#mobile-form", "#error-name-mobile", "#error-code-mobile", ".img-mobile", true)
)
$("#btn-reg").click(
submitFormByAjax($("#reg-form").attr("action"), "#reg-form", "#error-name", "#error-code", ".img", true)
)
</script>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
hello <span th:text="${username}">world!</span><br>
roles: <span th:text="${roles}"/>
<!-- 通过 th:action 的方式支持 csrf 或者 添加隐藏域<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> -->
<form th:action="@{/logout?logout}" method="post">
<input type="submit" value="退出登录post"/>
</form>
</body>
</html>
- 浏览器访问
http://localhost:9090/demo/login
, 至此集成了:登录校验,验证码、手机登录、第三方登录(JustAuth)、基于 RBAC 的 uri 访问权限控制功能, 签到等功能; 实现快速开发。 - 此
Quick Start
代码在demo 模块
-> quickStart, 其他功能的详细配置说明参照: Configurations。