大家好,这么久没更新文章,是不是有点想我了呢?
今天给大家带来的是Springboot+SpringSecurity在不使用模板引擎的情况下,怎么做登录功能!
Springboot官方是推荐使用模板引擎的,百度上搜索SpringSecurity登录,大多数的都是使用模板引擎;
由于现在的项目大多都是前后端分离的,所以在开发中,大概率也不可能去使用模板引擎去写前端。
这个功能使用的是Springboot+SpringSecurity+Layui去实现的,有些急性子的同学看到这里,害Layui,我没用过,关闭,继续寻找下一个文章,
喂喂喂,大哥不要走啊,不会layui也可以的,你不要着急嘛!
你可以使用原生的html和jquery去实现,效果都是一样的,使用vue也完全可以啦~
好了,说了这么多"废话,下面开始代码展示"
使用的是maven工程,把这些依赖引入。Springboot的版本是2.x,如果引入依赖报错,就把版本号添加上
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<!--mysql驱动你可以根据自己的本地mysql版本选择,我这里使用的8.x,你可以使用5.x-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
下面是实体类,和数据库的表字段对应,直接去建数据库表就行,数据库我就不再贴了。
由于使用了lombok,则这个setter/getter方法就可以用@Data代替了,如果不想用,直接生成setter/getter方法也可以
@Data
@TableName(value = "tb_user")
public class Authority implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField("username")
private String username;
@TableField("password")
private String password;
@TableField("phone")
private String phone;
@TableField("authority")
private String authority;
}
然后是dao层,由于我们使用的是mybatis-plus,所以不需要自己写sql语句了,mybatis-plus也是兼容mybatis的,所以没用过的这个的小伙伴,写sql语句也是一样的
public interface AuthorityDao extends BaseMapper<Authority> {
}
对,你没有看错,就是这么简单,继承一下BaseMapper就可以了,迷惑的同学可以点开看一下源码,鼠标放BaseMapper上面 ctrl + 鼠标左键,里面都是封装好的方法
然后就是service层,这里的service更普通的service有点不一样了,哪里不一样呢,等下解释
public interface AuthorityService extends UserDetailsService {
}
@Slf4j
@Service
public class AuthorityServiceImpl implements AuthorityService {
// 注入dao层接口
@Resource
private AuthorityDao authorityDao;
// 重写UserDetailsService中的方法
@Override
public UserDetails loadUserByUsername(String username) {
QueryWrapper<Authority> qw = new QueryWrapper<>();
qw.eq("username",username);
Authority authority = authorityDao.selectOne(qw);
if (null == authority) {
throw new UsernameNotFoundException("用户名不存在");
}
// 把用户名存入session,这里这个获取session的工具类,我也给大家放到下边
// 存入session是方便获取到登录的用户名
GetSessionUtils.getSession().setAttribute("loginUsername",authority.getUsername());
// 这个User是SpringSecurity提供的,而不是自己写的,只需要传入账号密码,和权限,SpringSecurity会自己判断账号密码,密码应该是加密过后的密码,而不是明文,如何得到密文,在文章末尾我会给出
return new User(authority.getUsername(),authority.getPassword(),getAuthority());
}
// 指定一个权限,拥有该权限的才能登录
// 数据库里边存的是 ADMIN,并不是 ROLE_ADMIN
private List<GrantedAuthority> getAuthority() {
return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
}
}
public class GetSessionUtils {
private GetSessionUtils(){}
public static HttpSession getSession() {
ServletRequestAttributes attributes
= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request.getSession();
}
}
?
我选择了使用一个接口去继承UserDetailsService,然后再实现,以便以后的方法的扩充,当然也可以直接实现UserDetailsService
controller层,这个控制层还是要写的
@Slf4j
@Controller
public class AuthorityController {
@GetMapping("/user/login/success")
@ResponseBody
public Msg loginSuccess() {
return Msg.success();
}
@GetMapping("/user/login/fail")
@ResponseBody
public Msg loginFail() {
return Msg.fail();
}
/**
* 获取登录用户名
*/
@GetMapping("/user/getUsername")
@ResponseBody
public Msg getUserName(HttpServletRequest request) {
return Msg.success().add("msg",request.getSession().getAttribute("loginUsername"));
}
}
Msg是统一的返回格式,代码如下
@Data
public class Msg implements Serializable {
private String msg;
private Integer code;
Map<String,Object> extend = new HashMap<>();
public static Msg success() {
Msg result = new Msg();
result.setMsg("处理成功");
result.setCode(200);
return result;
}
public static Msg fail() {
Msg result = new Msg();
result.setCode(100);
result.setMsg("处理失败");
return result;
}
public Msg add(String key, Object value) {
this.extend.put(key, value);
return this;
}
}
?
然后到了关键的配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPassword());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
// 指定前端input字段的名字,即name,不指定默认为username和password
.passwordParameter("username")
.passwordParameter("password")
// 自定义登录界面的地址
.loginPage("/login.html")
// 登录请求的处理的url,即ajax的url地址
.loginProcessingUrl("/user/login")
// 登录失败之后的处理接口,你也可以自定义handler处理
.failureUrl("/user/login/fail")
// 登录成功后默认的请求接口(contoller中的接口)
.defaultSuccessUrl("/user/login/success")
.and().authorizeRequests()
// 设置 user/login/** 接口谁都可以访问,否则没办法登录
.antMatchers("/user/login/**").permitAll()
// 拥有ADMIN的权限,可以肆意妄为,什么操作都能做
.antMatchers("/**").hasAnyRole("ADMIN")
.and().httpBasic()
// 跨域保护禁用掉
.and().csrf().disable();
// 关闭禁用frame框架,不关闭的话,不允许嵌套页面的出现,这个地方困扰了我很久
http.headers().frameOptions().disable();
// 退出登录的接口地址,以及退出登录之后,返回到哪个页面
http.logout().logoutUrl("/user/logout").logoutSuccessUrl("/login.html");
}
@Bean
HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
// 密码加密
@Bean
public BCryptPasswordEncoder getPassword() {
return new BCryptPasswordEncoder();
}
// 静态资源的放行规则,放行所有静态资源,并且放行登录页面
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**","/img/**",
"/js/**","/layui/**","/login.html");
}
}
上面的配置类的注释,我给大家补全了,相信大家能看懂,看不懂就留言,我回复的很及时
然后就是启动类
@EnableTransactionManagement
@EnableScheduling
@EnableCaching
// 这个地方是扫描多个dao包,如果只有一个dao包,就用没有注释的注解
// @MapperScan({"包1","包2"})
@MapperScan("自己的包名字")
@SpringBootApplication
// 开启SpringSecurity的支持
@EnableWebSecurity
// 开启全局SpringSecurity方法
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class PrisonSystemApplication {
public static void main(String[] args) {
SpringApplication.run(PrisonSystemApplication.class, args);
}
}
properties的配置文件我也给大家贴上吧,免得大家在去别的地方找;5.x的数据库,去掉第一行的cj即可,大家注意修改一下
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/login?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
#禁用缓存
spring.thymeleaf.cache=false
#配置要扫描的包(pojo)
mybatis.type-aliases-package=填写实体类的包位置
#开启Restful风格的支持
spring.mvc.hiddenmethod.filter.enabled=true
登录界面 (login.html),这个大家可以去网上找一个,或者自己写一个简单的登录页面,这个地方我引入了一些外部的样式。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="layui/css/layui.css">
<link rel="stylesheet" href="css/adminLogin.css">
<script src="layui/layui.js"></script>
</head>
<body style="background: url(img/1.jpg);">
<div class="wrap">
<div class="loginForm">
<form id="loginFormSubmit">
<div class="logoHead">
</div>
<div class="usernameWrapDiv">
<div class="usernameLabel">
<label>用户名:</label>
</div>
<div class="usernameDiv">
<i class="layui-icon layui-icon-username adminIcon"></i>
<input id="loginUsername" class="layui-input adminInput" type="text" name="username" placeholder="输入用户名" >
</div>
</div>
<div class="usernameWrapDiv">
<div class="usernameLabel">
<label>密码:</label>
</div>
<div class="passwordDiv">
<i class="layui-icon layui-icon-password adminIcon"></i>
<input id="loginPassword" class="layui-input adminInput" type="password" name="password" placeholder="输入密码">
</div>
</div>
<br/>
<div class="usernameWrapDiv">
<div class="submitDiv">
<input id="loginBtn" type="button" class="submit layui-btn layui-btn-primary" lay-submit lay-filter="login-submit" value="登录"/>
</div>
</div>
</form>
</div>
</div>
</body>
<script>
layui.use(['form', 'jquery'], function () {
var $ = layui.jquery;
var form = layui.form;
//登录提交
form.on('submit(login-submit)', function () {
$.ajax({
// 此处为配置类的登录处理接口
url:"/user/login",
// 提交方法必须是post
method:'post',
// 将整个form表单序列化,提交到后端
data:$("#loginFormSubmit").serialize(),
success:function (msg) {
// 状态码为 Msg类自定义的,当然你可以随意改动
if (msg.code == 100) {
layer.alert('账号或密码错误,请重试', {
icon: 5,
title: "提示"
});
}else {
// 登录成功后要跳转到的页面
window.location.href = "index.html"
}
},
error:function (err) {
console.log(err)
// 如果网页有图片没有,请删除或注释掉引入图片的代码,否则第一次点击会出现这个提示
layer.alert('发生了未知的错误,请联系管理员解决', {
icon: 5,
title: "提示"
});
}
});
});
});
</script>
</html>
css
/*登陆表单样式 start*/
.wrap{
width: 100%;
height: 100%;
/*background: url("../images/back.jpg") no-repeat;*/
background-size: cover;
}
.loginForm{
margin-left: 35%;
margin-top: 10%;
/*background-color: #cccccc;*/
background-color: #e7e7e7;
width: 400px;
height: 400px;
float: left;
z-index: 9999;
position: fixed;
opacity: 0.75;
}
.usernameDiv{
width: 300px;
height: 40px;
padding-left: 130px;
padding-top: 30px;
}
.adminInput{
width: 200px;
height: 40px;
font-size: 15px;
border-radius: 0.5em;
/*margin-left: auto;*/
/*border: 1px solid #cccccc;*/
}
.passwordDiv{
width: 300px;
height: 40px;
padding-left: 130px;
padding-top: 28px;
}
.cardDiv{
width: 120px;
height: 40px;
padding-top: 28px;
padding-left: 14px;
float: left;
}
.cardInput{
width: 124px;
height: 40px;
font-size: 15px;
border-radius: 0.5em 0em 0em 0.5em;
}
.codeDiv{
width: 100px;
height: 40px;
padding-top: 28px;
padding-right: 20px;
float: left;
}
.codeInput{
width: 80px;
height: 40px;
font-size: 15px;
border-radius: 0em 0.5em 0.5em 0em;
/*验证码样式*/
font-family: Arial;
font-style: italic;
font-weight: bold;
/*border: 0;*/
letter-spacing: 2px;
cursor: pointer;
}
i{
position: absolute;
}
.adminIcon{
font-size: 22px;
margin-top: 8px;
margin-left: 165px;
}
.logoHead{
width: 250px;
height: 60px;
padding-left: 90px;
padding-top: 25px;
}
.usernameLabel{
width: 60px;
height: 30px;
font-size: 16px;
float: left;
margin-left: 55px;
margin-top: 40px;
}
.submitLabel{
width: 160px;
height: 30px;
font-size: 13px;
float: left;
margin-left: 55px;
margin-top: 40px;
cursor: pointer;
}
.usernameWrapDiv{
width: 400px;
height: 70px;
}
.submitDiv{
width: 150px;
height: 40px;
padding-left: 10px;
padding-top: 28px;
float: left;
}
.submit{
width: 100px;
height: 40px;
border-radius: 0.5em;
}
img{
position: absolute;
}
.imgStyle{
width: 100%;
height: 100%;
}
/*登陆表单样式 end*/
/*注册页面样式 start*/
.registerPage{
width: 100%;
height: 100%;
/*background-color: #cccccc;*/
display: none;
opacity: 0.75;
}
.registerDiv{
width: 100%;
height: 100%;
z-index: 9999;
opacity: 0.75;
}
layui.js你可以使用jquery代替,我只不过是为了好看而已,而且大家是后端,暂时也没有必要去研究样式
注意:不能有不存在的图片链接,否则可以出现点击一次登录不成功的bug
?
平时大家做的登录,都会先经过控制层,而SpringSecurity则不会,你把提交的接口改为和配置类处理的接口相同,它就会自动的去执行Service层的loadUserByUsername方法,然后就是判断规则,注意密码,上面给大家提到了怎么存密文,如下面的代码,就能得到一个密文串,当然如果大家要注册功能的话,只需要在注册提交之后,在后端拿到明文密码再加密,最后存储即可。
?
public class BCTest {
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
System.out.println(bCryptPasswordEncoder.encode("123"));
}
}
登录失败效果展示(登录成功会跳转到指定的页面,就不在展示了):
密码输入错误,会有提示框,你也可以加汉字,在controller层返回的时候,在原有的返回代码后加?.add(key,value); 然后通过msg.extend.key 拿到value,填充到你想要指定的位置即可
?
好了,这篇文章就到这里啦,大家有什么问题,可以在下方留言,有什么错误的地方,欢迎指出。如果文章对你有帮助,请点个赞点个关注留下你的足迹吧!
?
?