Spring Boot Security 实现后台&权限管理系统(二)
Security改造登陆页
前面第一章我们做到了Spring Boot集成Security并成功运行,但是,出现了一个问题:
上图看到,Security内置的登陆页面实在过于简单,接下来我们就来改造登陆页面。
第一章中有解释过各个模块之间存放的相关文件,接下来的Java文件及配置将不再讲述放在哪个模块下,还不太清楚的小伙伴可以点击此处查看我的代码结构
application配置文件
spring:
security:
# 登陆路径
login-url: /login
# 登出路径
logout-url: /logout
# 免认证静态资源路径
anon-resources-url: /css/**,/js/**,/skin/**,/images/**,/font/**,/fonts/**,/dist/**
# 放行路径
release-url: /login
# 记住我超时时间
remember-me-timeout: 300
# 对应登录页面 form表单的 action属性
login-processing-url: /authentication/form
配置属性
/**
* @Package: com.zlx.bpms.properties
* @Author: LQW
* @Date: 2020/3/17
* @Description:权限认证属性
*/
@ConfigurationProperties(prefix = "spring.security")
@Data
public class BpmsSecurityProperties {
/**
* 登录路径
*/
private String loginUrl;
/**
* 登出路径
*/
private String logoutUrl;
/**
* 免认证静态资源路径
*/
private String anonResourcesUrl;
/**
* 放行路径
*/
private String releaseUrl;
/**
* 记住我超时时间
*/
private int rememberMeTimeout;
/**
* 处理登陆认证URL(页面的action属性值)
*/
private String loginProcessingUrl;
}
Security配置
/**
* @Package: com.zlx.bpms.config
* @Author: LQW
* @Date: 2020/3/17
* @Description:bpms安全配置
*/
@Configuration
@Order(-1) //值越小,优先级越高
@EnableWebSecurity
public class BpmsSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private BpmsSecurityProperties properties;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 放行路径
String[] releaseUrl = StringUtils.splitByWholeSeparatorPreserveAllTokens(properties.getReleaseUrl(), ",");
//免认证静态资源路径
String[] anonResources = StringUtils.splitByWholeSeparatorPreserveAllTokens(properties.getAnonResourcesUrl(), ",");
http
//开启http基本配置
.httpBasic()
.and()
//开启表单登陆方式
.formLogin()
//登陆的Url地址
.loginPage(properties.getLoginUrl())
//处理登陆认证URL(页面的action属性值)
.loginProcessingUrl(properties.getLoginProcessingUrl())
//放行登陆页面
.permitAll()
.and()
//开启授权配置
.authorizeRequests()
//放行路径
.antMatchers(releaseUrl).permitAll()
//免认证静态资源路径
.antMatchers(anonResources).permitAll()
//所有请求
.anyRequest()
//都需要认证
.authenticated();
}
控制器(controller)
/**
* @Package: com.zlx.bpms.controller
* @Author: LQW
* @Date: 2020/3/17
* @Description:系统登陆控制器
*/
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(){
return "login";
}
}
html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link th:href="@{css/shop.css}" type="text/css" rel="stylesheet"/>
<link th:href="@{skin/default/skin.css}" rel="stylesheet" type="text/css" id="skin"/>
<link th:href="@{css/Sellerber.css}" type="text/css" rel="stylesheet"/>
<link th:href="@{css/bkg_ui.css}" type="text/css" rel="stylesheet"/>
<link th:href="@{font/css/font-awesome.min.css}" rel="stylesheet" type="text/css"/>
<script data-th-src="@{js/jquery-1.9.1.min.js}" type="text/javascript"></script>
<script data-th-src="@{js/layer/layer.js}" type="text/javascript"></script>
<script data-th-src="@{js/bootstrap.min.js}" type="text/javascript"></script>
<script data-th-src="@{js/Sellerber.js}" type="text/javascript"></script>
<script data-th-src="@{js/shopFrame.js}" type="text/javascript"></script>
<script type="text/javascript" data-th-src="@{js/jquery.cookie.js}"></script>
<title>用户登录</title>
</head>
<body class="login-layout Reg_log_style">
<div class="logintop">
<span>欢迎后台管理界面平台</span>
<ul>
<li><a href="#">返回首页</a></li>
<li><a href="#">帮助</a></li>
<li><a href="#">关于</a></li>
</ul>
</div>
<div class="loginbody">
<div class="login-container">
<div class="center"><img th:src="@{images/logo.png}"/></div>
<div class="space-6"></div>
<div class="position-relative">
<div id="login-box" class="login-box widget-box no-border visible">
<div class="login-main">
<div class="clearfix">
<div class="login_icon"><img th:src="@{images/login_img.png}"/></div>
<form th:action="@{/authentication/form}" method="post"
style=" width:300px; float:right; margin-right:50px;">
<h4 class="title_name"><img th:src="@{images/login_title.png}"/></h4>
<fieldset>
<ul>
<li class="frame_style form_error"><label class="user_icon"></label><input
name="username"
type="text"
data-name="用户名"
id="username"/><i>用户名</i>
</li>
<li class="frame_style form_error"><label class="password_icon"></label><input
name="password" type="password" data-name="密码" id="userpwd"/><i>密码</i></li>
<li class="frame_style form_error"><label class="Codes_icon"></label><input
name="imageCode"
type="text"
data-name="验证码"
id="Codes_text"/><i>验证码</i>
<div class="Codes_region"><img th:src="@{images/yanzhengma.png}" width="100%"
height="38px"></div>
</li>
</ul>
<div class="space"></div>
<div class="clearfix">
<label class="inline">
<input type="checkbox" class="ace">
<span class="lbl">保存密码</span>
</label>
<button type="submit" class="login_btn" id="login_btn"> 登 陆</button>
</div>
<div class="space-4"></div>
</fieldset>
</form>
</div>
<div class="social-or-login center">
<span class="bigger-110">通知</span>
</div>
<div class="social-login ">
为了更好的体验性,本网站系统不再对IE8(含IE8)以下浏览器支持,请见谅。
</div>
</div><!-- /login-main -->
<!-- /widget-body -->
</div><!-- /login-box -->
</div><!-- /position-relative -->
</div>
</div>
<div class="loginbm">版权所有 2016 <a href=""></a></div>
<strong></strong>
</body>
</html>
<script type="text/javascript">
$('#login_btn').on('click', function () {
var num = 0;
var str = "";
$("input[type$='text'],input[type$='password']").each(function (n) {
if ($(this).val() == "") {
layer.alert(str += "" + $(this).attr("data-name") + "不能为空!", {
title: '提示框',
icon: 0,
});
num++;
return false;
}
});
if (num > 0) {
return false;
} else {
layer.alert('登陆成功!', {
title: '提示框',
icon: 1,
});
location.href = "shops_index.html";
layer.close(index);
}
});
$(document).ready(function () {
$("input[type='text'],input[type='password']").blur(function () {
var $el = $(this);
var $parent = $el.parent();
$parent.attr('class', 'frame_style').removeClass(' form_error');
if ($el.val() == '') {
$parent.attr('class', 'frame_style').addClass(' form_error');
}
});
$("input[type='text'],input[type='password']").focus(function () {
var $el = $(this);
var $parent = $el.parent();
$parent.attr('class', 'frame_style').removeClass(' form_errors');
if ($el.val() == '') {
$parent.attr('class', 'frame_style').addClass(' form_errors');
} else {
$parent.attr('class', 'frame_style').removeClass(' form_errors');
}
});
})
</script>
以上都完成后,我们的登陆页面就已经有了
是不是比之前的好看了许多,不要以为到这里就完了,我们只是把楼房的外表弄出来了,可里面还是空壳子呢!不信点点登陆按钮就知道了。
数据库实现登陆
到这里,我们点击了页面的登陆按钮发现并没有任何反应(但浏览器地址栏却多了一串</font color=#dd0000>?error),那是因为我们还没有实现登陆者身份验证。
UserDetailService
/**
* @Package: com.zlx.bpms
* @Author: LQW
* @Date: 2020/3/17
* @Description:用户详细信息服务接口实现类
*/
@Component("userDetailService")
public class UserDetailServiceImpl implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(UserDetailServiceImpl.class);
@Resource
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.getUserByUserName(username);
//查询该用户具有的权限
List<String> list = userService.findRolePermission(username);
log.info("通过用户名为:{},获取到的用户信息为:{}", username, user);
//region 实体类封装
UserDetail detail = new UserDetail();
if (null != user) {
detail.setUsername(user.getUserName());
detail.setPassword(user.getPassword());
//region 权限组装
Set<SimpleGrantedAuthority> authoritiesSet = new HashSet<SimpleGrantedAuthority>();
for (String role : list) {
SimpleGrantedAuthority roleAdmin = new SimpleGrantedAuthority(role);
authoritiesSet.add(roleAdmin);
}
//endregion
detail.setAuthorities(authoritiesSet);
}
//endregion
return detail;
}
}
用户信息
- User
/**
* @Package: com.zlx.bpms.bean
* @Author: LQW
* @Date: 2020/3/17
* @Description:权限认证用户实体
*/
@TableName("sys_user")
@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField("remark_name")
private String remarkName;
@TableField("user_name")
private String userName;
@TableField("password")
private String password;
@TableField("phone_number")
private String phoneNumber;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableField("is_status")
private Integer isStatus;
@Override
public String toString() {
return "User{" +
"id=" + id +
", remarkName='" + remarkName + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", createTime=" + createTime +
", updateTime=" + updateTime +
", isStatus=" + isStatus +
'}';
}
@Override
protected Serializable pkVal() {
return this.id;
}
}
- UserDetail
/**
* @Package: com.zlx.bpms.bean
* @Author: LQW
* @Date: 2020/3/17
* @Description:用户详细信息
*/
public class UserDetail implements UserDetails, Serializable {
private String username;
private String password;
private Set<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public String getPassword() { // 最重点Ⅰ
return this.password;
}
@Override
public String getUsername() { // 最重点Ⅱ
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
接口及实现类
- UserService
/**
* @Package: com.zlx.bpms.service
* @Author: LQW
* @Date: 2020/3/17
* @Description:权限认证用户接口服务
*/
public interface UserService {
/**
* 获取用户信息 by 用户名
*
* @param username 用户名
* @return User
*/
User getUserByUserName(String username);
/**
* 查找角色权限 by 用户名
*
* @param username 用户名
* @return list
*/
List<String> findRolePermission(String username);
}
- UserServiceImpl
/**
* @Package: com.zlx.bpms
* @Author: LQW
* @Date: 2020/3/17
* @Description:认证权限用户接口实现类
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public User getUserByUserName(String username) {
QueryWrapper<User> ew = new QueryWrapper<>();
ew.eq("user_name", username);
ew.eq("is_status", 1);
return userDao.selectOne(ew);
}
@Override
public List<String> findRolePermission(String username) {
return userDao.findRolePermission(username);
}
}
- UserDao
/**
* @Package: com.zlx.bpms.dao
* @Author: LQW
* @Date: 2020/3/17
* @Description:用户数据交互接口
*/
public interface UserDao extends BaseMapper<User> {
List<String> findRolePermission(@Param("username") String username);
}
- UserDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zlx.bpms.dao.UserDao">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.zlx.bpms.bean.User">
<id column="id" property="id"/>
<result column="remark_name" property="remarkName"/>
<result column="user_name" property="userName"/>
<result column="password" property="password"/>
<result column="phone_number" property="phoneNumber"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="is_status" property="isStatus"/>
</resultMap>
<sql id="overall_column">
id,remark_name,user_name,password,phone_number,create_time,update_time,is_status
</sql>
<select id="findRolePermission" resultType="java.lang.String">
SELECT
ap.role_permission
FROM
sys_permission AS ap
LEFT JOIN sys_user AS au ON ap.user_id = au.id
LEFT JOIN sys_role AS ar ON ar.permission_id = ap.id
AND ar.user_id = au.id
WHERE au.user_name = #{username,jdbcType=VARCHAR}
</select>
</mapper>
数据库及表设计在我GitHub项目中
修改Security配置
在BpmsSecurityConfig中,添加如下代码
@Resource
private UserDetailServiceImpl detailService;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
//设置密码编码器
provider.setPasswordEncoder(passwordEncoder());
//设置 用户详细信息服务接口
provider.setUserDetailsService((UserDetailsService) detailService);
// 关闭 隐藏未找到用户异常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
/**
* 身份验证管理器
*
* @param auth 身份验证管理器生成器
* @throws Exception 异常信息
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//目的是为了前端获取数据时获取到整个form-data的数据,提供验证器
auth.authenticationProvider(authenticationProvider());
//配置登录user验证处理器 以及密码加密器 好让认证中心进行验证
auth.userDetailsService((UserDetailsService) detailService).passwordEncoder(passwordEncoder());
}
到这里我们的登陆就已经顺利完成了,接下来可以登陆看看效果。由于时间不早了,我这里就不做展示了。