07 Spring Security 之 WebSecurityConfig

Spring Security介绍

大部分系统,都需要认证和鉴权的功能。SpringBoot常用的安全框架spring security和shiro。

shiro相对来说简单易用,spring security功能更完善一点。

本文介绍spring security的集成方法,以及使用数据库维护权限数据,包括用户、权限

使用数据库维护用户数据

数据库表设计

这里简化一下,直接实现User和Role的映射,而省略了Role和资源的映射。

CREATE TABLE `Users` (
  `UserId` int(11) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(45) NOT NULL,
  `PassWord` varchar(100) NOT NULL,
  `LockedFlag` tinyint(4) NOT NULL,
  PRIMARY KEY (`UserId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `UserRole` (
  `UserRoleId` int(11) NOT NULL AUTO_INCREMENT,
  `UserId` int(11) NOT NULL,
  `RoleId` int(11) NOT NULL,
  PRIMARY KEY (`UserRoleId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Role` (
  `RoleId` int(11) NOT NULL AUTO_INCREMENT,
  `RoleCode` varchar(45) NOT NULL,
  `RoleDesc` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`RoleId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Mybatis代码

如果你看过前几章mybatis方面的内容,可以直接自己实现一下,跳过Mybatis这部分内容。

bean层

//RoleBean
package com.it_laowu.springbootstudy.springbootstudydemo.bean;

import java.io.Serializable;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class RoleBean implements Serializable {
    private int  roleId;
    private String roleCode;
    private String roleDesc;
}
//UserBean
......省略package和import
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class UserBean implements Serializable {
    private int  userId;
    private String userName;
    private String passWord;
    private String lockedFlag;
}
//UserRoleBean
......省略package和import
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class UserRoleBean implements Serializable {
    private int  userRoleId;
    private int userId;
    private int roleId;
}

Condition层

//RoleCondition
package com.it_laowu.springbootstudy.springbootstudydemo.bean.condition;

import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.BaseCondition;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class RoleCondition extends BaseCondition {
    private int  roleId;
    private String roleCode;
    private String roleDesc;

    @Override
    public Class<?> getChildClass() {
        return RoleBean.class;
    }
}
//UserCondition
......省略package和import
@Data
@Accessors(chain = true)
public class UserCondition extends BaseCondition {
    private int  userId;
    private String userName;
    private String passWord;
    private String lockedFlag;

    @Override
    public Class<?> getChildClass() {
        return UserBean.class;
    }
}
//UserRoleCondition
......省略package和import
@Data
@Accessors(chain = true)
public class UserRoleCondition extends BaseCondition {
    private int  userRoleId;
    private int userId;
    private int roleId;

    @Override
    public Class<?> getChildClass() {
        return UserRoleBean.class;
    }
}

dao层

//RoleDao
package com.it_laowu.springbootstudy.springbootstudydemo.dao;

import java.util.List;

import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.condition.RoleCondition;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.IBaseDao;

import org.apache.ibatis.annotations.Param;


public interface RoleDao extends IBaseDao<RoleBean,RoleCondition> {
    List<RoleBean> getUserRolesByUserId(@Param("keyId") int userId);
}
//UserDao
......省略package和import
public interface UserDao extends IBaseDao<UserBean,UserCondition> {
    UserBean findByName(@Param("username") String username);
}
//UserRoleDao
......省略package和import
public interface UserRoleDao extends IBaseDao<UserRoleBean,UserRoleCondition> {
}

service层

//IRoleService
package com.it_laowu.springbootstudy.springbootstudydemo.service;

import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.condition.RoleCondition;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.IBaseService;

public interface IRoleService extends IBaseService<RoleBean,RoleCondition>  {
    List<RoleBean> getUserRolesByUserId(@Param("keyId") int userId);
}
//IUserRoleService
......省略package和import
public interface IUserRoleService extends IBaseService<UserRoleBean,UserRoleCondition>  {
}
//IUserService
......省略package和import
public interface IUserService extends IBaseService<UserBean,UserCondition>  {
    UserBean findByName(@Param("username") String username);
}

ServiceImpl层

//RoleServiceImpl
package com.it_laowu.springbootstudy.springbootstudydemo.service.impl;

import java.util.List;

import com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean;
import com.it_laowu.springbootstudy.springbootstudydemo.bean.condition.RoleCondition;
import com.it_laowu.springbootstudy.springbootstudydemo.core.base.IBaseDao;
import com.it_laowu.springbootstudy.springbootstudydemo.dao.RoleDao;
import com.it_laowu.springbootstudy.springbootstudydemo.service.IRoleService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RoleServiceImpl implements IRoleService {
    @Autowired
    public RoleDao roleDao;

    @Override
    public IBaseDao<RoleBean, RoleCondition> getBaseDao() {
        return roleDao;
    }
    @Override
    public List<RoleBean> getUserRolesByUserId(int userId) {
        return roleDao.getUserRolesByUserId(userId);
    }
}
//UserRoleServiceImpl
......省略package和import
@Service
public class UserRoleServiceImpl implements IUserRoleService {
    @Override
    public IBaseDao<UserRoleBean, UserRoleCondition> getBaseDao() {
        return userRoleDao;
    }
}
//UserServiceImpl
......省略package和import
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    public UserDao userDao;

    @Override
    public IBaseDao<UserBean, UserCondition> getBaseDao() {
        return userDao;
    }
    @Override
    public UserBean findByName(String username) {
        return userDao.findByName(username);
    }
}

mybatis.xml

role.mybatis.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.it_laowu.springbootstudy.springbootstudydemo.dao.RoleDao">
    <resultMap id="RoleResultMap" type="com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean">
        <result column="roleId" property="roleId"/>
        <result column="roleCode" property="roleCode"/>
        <result column="roleDesc" property="roleDesc"/>
    </resultMap>

    <select id="findAll" resultMap="RoleResultMap">
        select roleId,roleCode,`roleDesc`
        from `Role`
        <where>
            <if test="conditionQC.roleId != 0">
                and roleId = #{conditionQC.roleId}
            </if>
            <if test="conditionQC.roleCode != null and '' != conditionQC.roleCode">
                and roleCode like concat('%',#{conditionQC.roleCode},'%')
            </if>
            <if test="conditionQC.roleDesc != null and '' != conditionQC.roleDesc">
                and roleDesc like concat('%',#{conditionQC.roleDesc},'%')
            </if>
        </where>
        <choose>
            <when test="conditionQC.sortSql == null">
                Order by roleId
            </when>
            <otherwise>
                ${conditionQC.sortSql}
            </otherwise>
        </choose>
    </select>

    <select id="findOne" resultMap="RoleResultMap">
        select roleId,roleCode,`roleDesc`
        from `Role`
        where roleId = #{keyId}
    </select>

    <select id="getUserRolesByUserId" resultMap="RoleResultMap">
        select r.roleId,r.roleCode,r.`roleDesc`
        from `Role` r
        left join `UserRole` ur ON r.RoleId = ur.RoleId
        where ur.UserId = #{keyId}
    </select>

    <insert id="insert" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean">
        insert into `Role`(roleId,`roleCode`,`roleDesc`)
        values(#{roleId},#{roleCode},#{roleDesc})
    </insert>

    <update id="update" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.RoleBean">
        update `Role`
        <set>
            <if test="roleCode!=null"> `roleCode`=#{roleCode}, </if>
            <if test="roleDesc!=null"> `roleDesc`=#{roleDesc}, </if>
        </set>
        where roleId = #{roleId}
    </update>

    <delete id="delete" parameterType="int">
        delete from `Role` where roleId = #{keyId}
    </delete>

</mapper>

user.mybatis.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.it_laowu.springbootstudy.springbootstudydemo.dao.UserDao">
    <resultMap id="UserResultMap" type="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserBean">
        <result column="userId" property="userId"/>
        <result column="userName" property="userName"/>
        <result column="passWord" property="passWord"/>
        <result column="lockedFlag" property="lockedFlag"/>
    </resultMap>

    <select id="findAll" resultMap="UserResultMap">
        select userId,userName,`passWord`,lockedFlag
        from `Users`
        <where>
            <if test="conditionQC.userId != 0">
                and userId = #{conditionQC.userId}
            </if>
            <if test="conditionQC.userName != null and '' != conditionQC.userName">
                and userName like concat('%',#{conditionQC.userName},'%')
            </if>
            <if test="conditionQC.passWord != null and '' != conditionQC.passWord">
                and passWord like concat('%',#{conditionQC.passWord},'%')
            </if>
            <if test="conditionQC.lockedFlag != -1">
                and lockedFlag = #{conditionQC.lockedFlag}
            </if>
        </where>
        <choose>
            <when test="conditionQC.sortSql == null">
                Order by userId
            </when>
            <otherwise>
                ${conditionQC.sortSql}
            </otherwise>
        </choose>
    </select>

    <select id="findOne" resultMap="UserResultMap">
        select userId,userName,`passWord`,lockedFlag
        from `Users`
        where userId = #{keyId}
    </select>
    <select id="findByName" resultMap="UserResultMap">
        select userId,userName,`passWord`,lockedFlag
        from `Users`
        where userName = #{username}
    </select>    

    <insert id="insert" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserBean">
        insert into `Users`(userId,`userName`,`passWord`,lockedFlag)
        values(#{userId},#{userName},#{passWord},#{lockedFlag})
    </insert>

    <update id="update" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserBean">
        update `Users`
        <set>
            <if test="userName!=null"> `userName`=#{userName}, </if>
            <if test="passWord!=null"> `passWord`=#{passWord}, </if>
            <if test="lockedFlag!=null"> `lockedFlag`=#{lockedFlag}, </if>
        </set>
        where userId = #{userId}
    </update>

    <delete id="delete" parameterType="int">
        delete from `Users` where userId = #{keyId}
    </delete>

</mapper>

UserRole.mybatis.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.it_laowu.springbootstudy.springbootstudydemo.dao.UserRoleDao">
    <resultMap id="UserRoleResultMap" type="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserRoleBean">
        <result column="userRoleId" property="userRoleId"/>
        <result column="userId" property="userId"/>
        <result column="roleId" property="roleId"/>
    </resultMap>

    <select id="findAll" resultMap="UserRoleResultMap">
        select userRoleId,userId,`roleId`
        from `UserRole`
        <where>
            <if test="conditionQC.userRoleId != 0">
                and userRoleId = #{conditionQC.userRoleId}
            </if>
            <if test="conditionQC.userId != 0">
                and userId = #{conditionQC.userId}
            </if>
            <if test="conditionQC.roleId != 0">
                and roleId = #{conditionQC.roleId}
            </if>
        </where>
        <choose>
            <when test="conditionQC.sortSql == null">
                Order by userRoleId
            </when>
            <otherwise>
                ${conditionQC.sortSql}
            </otherwise>
        </choose>
    </select>

    <select id="findOne" resultMap="UserRoleResultMap">
        select userRoleId,userId,`RoleId`
        from `UserRole`
        where userRoleId = #{keyId}
    </select>

    <insert id="insert" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserRoleBean">
        insert into `UserRole`(`userId`,`roleId`)
        values(#{userId},#{roleId})
    </insert>

    <update id="update" parameterType="com.it_laowu.springbootstudy.springbootstudydemo.bean.UserRoleBean">
        update `UserRole`
        <set>
            <if test="userId!=0"> `userId`=#{userId}, </if>
            <if test="roleId!=0"> `roleId`=#{roleId}, </if>
        </set>
        where userRoleId = #{userRoleId}
    </update>

    <delete id="delete" parameterType="int">
        delete from `UserRole` where userRoleId = #{keyId}
    </delete>

</mapper>

Controller

因为Controller越来越多,而且都是实现Crud的功能,重复使用的频率很高,所以做了个BaseController

package com.it_laowu.springbootstudy.springbootstudydemo.core.base;

import java.util.List;

import com.it_laowu.springbootstudy.springbootstudydemo.core.customexceptions.ConflictException;
import com.it_laowu.springbootstudy.springbootstudydemo.core.pagehelper.PageInfo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

public abstract class BaseController<Bean,Condition,IService extends IBaseService<Bean,Condition>> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired(required = false)
    public IService baseService;

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public List<Bean> orders(Condition condition,PageInfo pageinfo) {
        return baseService.findAll(condition,pageinfo);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Bean order(@PathVariable(value = "id") int id) {
        return baseService.findOne(id);
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public int add(@Validated @RequestBody Bean bean) {
        return baseService.insert(bean);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public int update(@PathVariable(value = "id") int id, @RequestBody Bean bean) {
        setBeanKeyId(bean, id);
        return baseService.update(bean);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public int delete(@PathVariable(value = "id") int id) {
        return baseService.delete(id);
    }

    //其实就是update时,设置一下主键
    protected abstract void setBeanKeyId(Bean bean,int keyId) ;
}

有了这个,其他Controller就简单了,比如:

@RestController
@RequestMapping(value = "/user")
public class UserController extends BaseController<UserBean,UserCondition,IUserService>{
    @Override
    protected void setBeanKeyId(UserBean bean, int keyId) {
        bean.setUserId(keyId);
    }
}

维护用户数据时,记得加密密码。

这里提供一个密码 admin,加密后:$2a$10$xQI1JKsTrkemGxEK1mpsAezGQeTDpSNVjfMCLrhgf8ImpCE2hKI1S

maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

springboot具有自动装配机制,只要引入依赖,即使没配置,也会启动spring security,配置一套默认的权限。
现在启动项目,Console会自动生成用户User的密码,访问API会弹出登录提示界面,登录后可以自由访问页面。

自动生成密码

Security代码

以上都是数据库维护用户权限数据的代码。

下面的代码是集成Spring Security会用到的一些主要的代码:

  1. WebSecurityConfigurerAdapter:实现Security的各种配置
  2. AuthenticationProvider:通过重写authenticate方法,实现用户的校验。
  3. UserDetailsService:通过重写loadUserByUsername方法,可以实现在数据库中配置用户权限。
  4. MyAuthenticationFailureHandlerMyAuthenticationSuccessHandler:登录成功/失败反馈。
  5. resources/static/auth/login.html:自制比较友好的登录页面。
  6. resources/static/auth/logout.html:自制比较友好的登出提示页面。
  7. AdminController:设置了登录权限的一个页面,提供完整的访问测试。

配置文件WebSecurityConfigurerAdapter

这个类提供了Security的各种开关、特性的配置。

首先,新建一个WebSecurityConfigurerAdapter的实现类:

package com.it_laowu.springbootstudy.springbootstudydemo.core.config;

import com.it_laowu.springbootstudy.springbootstudydemo.core.auth.MyAuthenticationFailureHandler;
import com.it_laowu.springbootstudy.springbootstudydemo.core.auth.MyAuthenticationProvider;
import com.it_laowu.springbootstudy.springbootstudydemo.core.auth.MyAuthenticationSuccessHandler;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationProvider myAuthenticationProvider;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(myAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.httpBasic()
            .and()
        .authorizeRequests()
            .antMatchers("/user/**").permitAll()
            .antMatchers("/role/**").permitAll()
            .antMatchers("/userrole/**").permitAll()
            .antMatchers("/order/**").permitAll()
            .antMatchers("/auth/**").permitAll()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            //默认是/login,不用改
            .loginPage("/login")
            .successHandler(myAuthenticationSuccessHandler)
            .failureHandler(myAuthenticationFailureHandler)
            .permitAll()
            .and()
        .logout()
            //默认为/logout,不用改
            .logoutUrl("/logout")  
            .logoutSuccessUrl("/auth/logout.html") //默认跳转到 /login
            .deleteCookies("JSESSIONID");   //默认也是会删除cookie的
     }
}
}

针对这部分代码做一些简单的说明:

  • authorizeRequests:规定了哪些页面需要特定权限,哪些需要用户登录,哪些不需要。
  • formLogin:定义了API登录页面,以及成功失败的反馈处理(Handler)
  • logout:和login一样,大部分使用默认参数即可。
  • passwordEncoder:注入一个加密器,之前提供的密码,也是用这个加密器加密的。

认证相关代码

MyAuthenticationProvider:用户认证

package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private MyUserDetailsService myUserDetailsService;
    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //根据输入的用户密码,读取数据库中信息
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        MyUserDetails user = (MyUserDetails) myUserDetailsService.loadUserByUsername(username);

        //判断是否有效用户
        if (!user.isEnabled()) {
            throw new DisabledException("USER DISABLE");
        } else if (!user.isAccountNonLocked()) {
            throw new LockedException("USER LOCKED");
        } else if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("USER EXPIRED");
        } else if (!user.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException("USER LOGOUT");
        }

        //验证密码
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BadCredentialsException("PASSWORD INVALID!");
        }

        logger.info(String.format("用户%s登录成功", username));

        return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

MyUserDetailsService:用户数据处理,这里是和数据库交互的关键点

package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private IUserService userService;
    @Autowired
    private IRoleService roleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserBean bean =  userService.findByName(username);
        if (bean == null){
            throw new UsernameNotFoundException("User "+ username +" didn't exist.");
        }
        //获得角色信息
        List<RoleBean> roles = roleService.getUserRolesByUserId(bean.getUserId());
        //格式转化
        List<GrantedAuthority> authority = roles.stream().map(i->new SimpleGrantedAuthority(i.getRoleCode())).collect(Collectors.toList());
        return new MyUserDetails(bean.getUserName(),bean.getPassWord(),bean.getLockedFlag()==1,authority);
    }
}

MyUserDetails:UserDetails实现类,保存数据

package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;

import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@SuppressWarnings("serial")
public class MyUserDetails implements UserDetails {

    protected Integer id;
    private String username;
    private String password;
    private boolean lockedFlag;

    public MyUserDetails(String username, String password, boolean lockedFlag,Collection<? extends GrantedAuthority> roles) {
        this.username = username;
        this.password = password;
        this.lockedFlag = lockedFlag;
        this.authorities = roles;
    }

    public Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return  !lockedFlag;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}

MyAuthenticationSuccessHandler:认证成功反馈

package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyAuthenticationSuccessHandler  extends SavedRequestAwareAuthenticationSuccessHandler{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        //登录成功返回
        ResultBody resultBody = new ResultBody("200", "登录成功");
        //设置返回请求头
        response.setContentType("application/json;charset=utf-8");
        //写出流
        PrintWriter out = response.getWriter();
        ObjectMapper mapper = new ObjectMapper();  
        out.write(mapper.writeValueAsString(resultBody));
        out.flush();
        out.close();
    }
}

MyAuthenticationFailureHandler:认证失败反馈

package com.it_laowu.springbootstudy.springbootstudydemo.core.auth;
......省略import
@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //登录失败信息返回
        ResultBody resultBody = new ResultBody("401", "登录失败:"+exception.getLocalizedMessage());
        //设置返回请求头
        response.setContentType("application/json;charset=utf-8");
        //写出流
        PrintWriter out = response.getWriter();
        ObjectMapper mapper = new ObjectMapper();  
        out.write(mapper.writeValueAsString(resultBody));
        out.flush();
        out.close();
    }
}

杂项代码

/resources/static/auth/login.html

就是提供一个友好的登录页面,方便测试。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>

<body>

    <h2>Login</h2>
    <form id="loginForm" action="/login" method="post">
        用户:<input type="text" id="username" name="username"><br /><br />
        密码:<input type="password" id="password" name="password"><br /><br />
        <button id="btnlogin" type="button">Login</button>
    </form>
    <span id="msg" name="msg"></h2>

    <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
    <script type="text/javascript">

        $("#btnlogin").click(function () {
            $.ajax({
                type: "POST",
                url: "/springboot-study-demo/login",
                data: $("#loginForm").serialize(),
                dataType: "JSON",
                success: function (data) {
                    console.log(data);
                    $("#msg").text(data.message);
                }
            });

        });

    </script>
</body>

</html>

/resources/static/auth/logout.html

登出成功的反馈页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Logout</title>
</head>
<body>
    <h2>Log out success !!</h2>
</body>
</html>

AdminController

业务Controller,所有url均需登录用户。

@RestController
@RequestMapping(value = "/admin")
public class AdminController {

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String info(@PathVariable(value = "id") int id) {
        return "i am an administrator";
    }
}

测试一下

  • 访问/admin/1,直接跳转到/login,访问失败
  • 访问/auth/login.html,先测密码错误,再测成功。
  • 访问/admin/1,返回i am an administrator
  • 访问/logout,跳转到/auth/logout.html.
  • 访问/admin/1,直接跳转到/login再次访问失败

这里要注意,部分浏览器(比如Chrome)会自动记录用户密码,导致logout后,再次访问/admin/1还是成功的。使用火狐就没这个问题。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容